[토비의 스프링 3.1 Vol. 1][1장. 오브젝트와 의존관계] 2절. DAO의 분리
앞서 클래스 상속을 통해 관심사 분리를 했었다.
이번에는 서브 클래스가 아닌 아예 독립된 클래스로 분리해보자.
DB 커넥션 생성 기능을 전담하는 SimpleConnectionMaker 클래스를 만든다.
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFouncException, SQLException {
Class.forName(“com.mysql.jdbc.Driver”);
Connection c = DriverManager.getConnection(“jdbc:mysql://localhost/springbook”, “spring”, “book”);
return c;
}
}
그리고 UserDao 에서 SimpleConnectionMaker 를 사용하도록 수정한다.
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnectioin();
…
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = simpleConnectionMaker.makeNewConnection();
…
}
}
그런데 문제가 생겼다.
더 이상 AUserDao 와 BUserDao 가 상속을 통해 DB 커넥션 기능을 확장하여 사용하지 못하게 된 것이다.
왜냐하면 UserDao 가 SimpleConnectionMaker 클래스를 생성하여 사용하기 때문에 종속되었기 때문이다.
UserDao 가 SimpleConnectionMaker 라는 구체적인 클래스를 알고 있는것이 문제다.
그래서 중간에 느슨한 연결 고리인 인터페이스를 도입해보자.
그러면 UserDao 는 자신이 사용하는 클래스가 어떤 것인지 몰라도 된다.
단지, 인터페이스를 통해 원하는 기능을 사용하기만 하면 된다.
그러면 먼저 ConnectionMaker 인터페이스를 정의해보자.
public interface ConnectionMaker() {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
그러면 이제 AUserDao 와 BUserDao 에서 상속을 통해 DB 커넥션 생성 기능을 확장했던 부분이 ConnectionMaker 라는 인터페이스 구현의 영역으로 옮겨오게 된다.
그러면 AConnectionMaker 를 만들어보자.
public class AConnectionMaker implements ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException {
// Connection 생성 코드
}
}
이제 ConnectionMaker 를 사용하도록 UserDao 를 개선해보자.
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new AConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
…
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
…
}
}
그런데 위 코드에는 문제가 있다.
바로 생성자에서 AConnectionMaker 클래스가 명시적으로 드러난다는 것이다.
이러면 인터페이스를 도입함으로써 얻으려했던 UserDao 의 재사용성이 떨어지기 때문에 해결이 필요하다.
이럴 땐 생성자에서 파라미터로 ConnectionMaker 인터페이스 객체를 받도록 수정하자.
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
이제 클라이언트 측에서 구체적인 ConnectionMaker 객체를 만들어서 UserDao 인스턴스를 생성할 때 파라미터로 전달해주어야 한다.
UserDao 와 ConnectionMaker 사이의 관계 매핑을 해주는 클라이언트인 UserDaoTest 코드를 보자.
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ConnectionMaker connectionMaker = new AConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);;
…
}
}
엉겁결에 UserDaoTest 는 UserDao 와 ConnectionMaker 구현 클래스와의 런타임 의존 관계를 설정하는 책임을 담당하게 되었다.
하지만 덕분에 UserDao 의 작업을 UserDaoTest 클라이언트로 떠넘기고 나니 UserDao 는 변경없이 재사용가능한 코드가 되었다.
만약 ConnectionMaker 를 BConnectionMaker 로 바꾸고 싶다면 클라이언트 코드에서 한 줄만 수정해주면 된다.
이렇게 인터페이스를 도입하고 클라이언트의 도움을 받는 방법은 상속을 사용한 방식에 비해 훨씬 유연하다.