본문 바로가기
Programming/Spring

[오브젝트와 의존관계] - 관심사의 분리

by 공부합시다홍아 2025. 5. 3.

지난 문서에서는 DAO, Data Access Obejct란 무엇인가. 그리고 과정은 어떻게 되는 가에 대해서 알아보았습니다.

지난 문서를 다시 확인하고 싶으시다면 아래 링크를 클릭 하시면 DAO에 대해서 다시 같이 학습할 수  있습니다.

 

오브젝트와 의존관계 - DAO

개발 1년차. 요즘 기본기와 기초가 많이 부족한 거 같다는 생각을 많이한다. 누구는 그렇게까지 깊게 공부하지 않아도 GPT가 다 해준다라는 속 편한 말을 하는 직장 동료도 있지만 꾸준히 하다보

lifeisstudy-hong.tistory.com


오늘은 지난 학습 때 들었던 의문점인 데이터베이스가 변경되게 된다면 그 수 많은 기능에 대한 Connection을 전부 변경해줘야 하나? 라는 의문에 대해 관심사를 분리함으로써, 데이터베이스 연결과 기능 수행 부분을 분리하는 것에 대해 학습하였습니다.

 

관심사의 분리

UserDAO에서 구현된 메소드를 살펴보면, 적어도 세 가지의 관심사항을 발견할 수 있다.

public class UserDAO {
	
	// 사용자 추가
	public void add(User user) throws ClassNotFoundException, SQLException {
		
		Class.forName("com.mysql.jdbc.Driver");
		// 연결문자열 , DB_ID, DB_PW
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
		PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)" );
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());
		
		// update 의 반환자는 int 형으로 0 또는 1이다.
		ps.executeUpdate();
		
		ps.close();
		
		c.close();
		
	}
	
	// 사용자 조회
	public User get(String id) throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		// 연결문자열 , DB_ID, DB_PW
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
		PreparedStatement ps = c.prepareStatement("select * from users where id = ?" );
		
		ps.setString(1, id);
		
		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}
	
	
}
  • 첫 번째는 데이터베이스와의 연결을 위한 Connection 영역
  • 두 번째는 데이터베이스에 어떤 쿼리를 전달할 지에 대한 Statement 영역
  • 세 번째는 작업이 끝난 이후에 리소스를 처리하기 위한 Close 영역

위 세 가지의 관심사항이 반복된다는 것을 알 수 있었다.

 

중복 코드의 메소드 추출 필요

계속 반복되는 코드들을 추출하고 분리함으로써 미래의 변화에도 쉽게 대응할 수 있게 할 수 있다.

반복되는 코드는 아래와 같이 추출하면 된다.

UserDAO

private Connection getConnection() throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		// 연결문자열 , DB_ID, DB_PW
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
		return c;
	}
	
	// 사용자 추가
	public void add(User user) throws ClassNotFoundException, SQLException {
		
		Connection c = getConnection();
		PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)" );
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());
		
		// update 의 반환자는 int 형으로 0 또는 1이다.
		ps.executeUpdate();
		
		ps.close();
		
		c.close();
		
	}

 

위와 같이 Connection 부분을 공통의 관심사항으로 분리하여 사용하면, 데이터베이스가 변하게 되거나, 계정이 변경이 되더라도 변화에 쉽게 대응할 수 있게된다.

 

리펙토링 ( Refactoring )

위와 같이 관심사항을 분리하는 작업을 리펙토링이라고 한다. 또한 위에서 사용한 getConnection()이라고 하는 공통의 기능을 담당하는 메소드로 중복된 코드를 뽑아내는 것을 리팩토링에서는 메소드 추출(Extract Method) 기법이라고 한다.

 

이제 여기서 바로 다음 의문이 들 수 있다.

예를 들어, 내가 제공하고자 하는 서비스가 독립적인 하나의 솔루션이라고 하면 이해가 가능하지만 내 시스템을 다른 사람이 사용을 하는데 데이터베이스가 다르다면...? 어떻게 하지?

 

결론은 누가 접근을 하더라도 독립적으로 사용할 수 있게 확장성을 주어야 한다. 즉, 한 단계 더 나아가 DB 커넥션을 완전히 독립적으로 사용할 수 있다.

 

만약 N사와 D사가 별도의 데이터베이스를 사용하고자 한다면, 위와 같이 하나의 커넥션을 사용하는 것엔 한계가 있을 것이다. 위 문제를 해결하기 위해서 상속이라는 것을 사용한다.

 

상속을 위한 확장

위에서 만든 UserDAO에서 메소드의 구현 코드를 제거하고 getConnection()을 추상메소드로 만들어 놓는다.

추상메서드이기 때문에 메소드의 코드는 없지만 메소드 그 자체는 존재하게 된다.

출처 : 토비의 스프링

UserDAO

public abstract class UserDAO {
	
	public abstract Connection getConnection() throws ClassNotFoundException, SQLException;
	
}

 

NuserDAO

public class NuserDAO extends UserDAO{

	@Override
	public Connection getConnection() throws ClassNotFoundException, SQLException {
		
		Class.forName("com.mysql.jdbc.Driver");
		// 연결문자열 , DB_ID, DB_PW
		Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/jspdb", "root", "1234" );
		return c;

	}
	
	// 사용자 추가
	public void add(User user) throws ClassNotFoundException, SQLException {
		
		Connection c = getConnection();
		PreparedStatement ps = c.prepareStatement("insert into users(id,name,password) values (?,?,?)" );
		ps.setString(1, user.getId());
		ps.setString(2, user.getName());
		ps.setString(3, user.getPassword());
		
		// update 의 반환자는 int 형으로 0 또는 1이다.
		ps.executeUpdate();
		
		ps.close();
		
		c.close();
		
	}
	
	// 사용자 조회
	public User get(String id) throws ClassNotFoundException, SQLException{
		
		Connection c = getConnection();
		PreparedStatement ps = c.prepareStatement("select * from users where id = ?" );
		
		ps.setString(1, id);
		
		ResultSet rs = ps.executeQuery();
		rs.next();
		User user = new User();
		user.setId(rs.getString("id"));
		user.setName(rs.getString("name"));
		user.setPassword(rs.getString("password"));
		
		rs.close();
		ps.close();
		c.close();
		
		return user;
	}

	
}

 

일단 N사를 예를 들어서 NuserDAO를 만들어놓았고, DuserDAO도 별도로 존재한다고 생각하면 된다.

수정한 코드를 보면, 두 가지의 관심사항이 존재한다.

  • 어떻게 데이터를 등록하고 가져올 것인가라는 관심을 가지는 UserDAO
  • DB 연결 방법은 어떻게 할 것인가라는 관심을 가지는 NuserDAO와 DuserDAO

두 개의 DAO는 각 각 독립적으로 분리되면서 변경 작업은 한층 용이해지게 된다.

 

어느 환경이든 기본적인 프레임을 가진 상태로 상속을 받으며 변화에 대응이 가능하게 된다.

지금까지 데이터 엑세스 로직을 어떻게 만들 것인가와 DB 연결을 어떤 방법으로 할 것인가라는 두 개의 관심을 상하위 클래스로 분리시켰다.

이 두 개의 관심은 변화의 성격이 다르고, 그것은 변화의 이유와 시기 그리고 주기 등이 다르다는 뜻이다.

728x90