Programming/Spring

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

공부합시다홍아 2025. 5. 3. 16:10

지난 문서에서는 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