1. 클래스의 분리
관심사의 분리보다 더 화끈하게 분리를 해보자. DB커넥션과 관련된 부분을 서브클래스가 아니라 아예 별도의 클래스에 담는다. 이 클래스를 UserDao가 이용하게 한다.
예를 들어 별도의 SimpleConnectionMaker 클래스에 DB커넥션 코드를 작성하고 상속이 아닌 생성자를 통해 각 메소드에서 DB 커넥션을 가져오게 한다.
<SimpleConnectionMaker>
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost/springbook", "spring", "book");
return c;
}
}
<UserDao>
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
publ ic UserDao() {
simpleConnection삐aker = new SimpleConnectionlaker();
}
public void add(User user) throws ClassNotFoundException, SQLException (
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException (
Connection c = simpleConnectionMaker.makeNewConnection();
...
}
}
분리는 잘 했지만 다른 문제가 발생한다. 다른 방식으로 DB 커넥션을 제공하는 클래스를 사용하기 위해서는 UserDao 소스코드의 수정해야한다. 발생하는 문제를 요약하면
첫번째, SimpleConnectionMaker의 메소드가 문제다. makeNewConnection()을 사용해 DB 커넥션을 가져오게 했는데, 만약 D사에서 만든 DB 커넥션 제공 클래스는 openConnection()이라는 메소드 이름을 사용했다면 UserDao 내에 있는 add(), get() 메소드의 커넥션을 가져오는 코드를 일일이 변경해야 한다. 수십, 수백개가 된다면 작업의 양이 너무 커지게 된다
두번째, DB 커넥션을 제공하는 클래스가 어떤 것인지 UserDao가 구체적으로 알고 있어야 한다는 점이다. UserDao에 SimpleConnectionMaker라는 클래스 타입 인스턴스 변수까지 정의해놓고 있으니, N사에서 다른 클래스를 구현하면 어쩔 수 없이 UserDao 자체를 수정해야 한다.
즉, UserDao가 SimpleConnectionMaker라는 특정 클래스와 그 토드에 종속적이기 때문에 고객이 DB 커넥션을 가져오는 방법을 자유롭게 확장하기 힘들어졌다. 상속을 이용한 방법만도 못하다.
2. 인터페이스의 도입
클래스를 분리하면서 이런 문제를 해결하기위해 추상화(공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업)를 해준다. 이때 가장 유용한 도구가 인터페이스다. 인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모드 감춘다. 인터페이스를 통해 정급하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.
<ConnectionMaker 인터페이스>
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
<ConnectionMaker 구현 클래스>
public class DConnectionMaker implements ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException {
// D 사의 독자적인 방법으로 Connection 을 생성하는 코드
}
}
<UserDao>
public class UserDao {
private ConnectionMaker connectionMaker; ㅡ> 인터페이스를 통해 오브젝트에 접근, so 구체적 클래스 정보가 필요 없다.
public UserDao(){
connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
ㅡ> 인터페이스에 정의된 메소드를 사용하므로 클래스가 바뀐다고 메소드 이름이 변경 되지 않는다.
}
public user get(String id) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection();
}
}
단, Dconnection 클래스의 생성자를 호출해서 오브젝트를 생성하는 코드는 여전히 UserDao에 남아있다. 여전히 UserDao 소스코드를 함께 제공해서, 필요할 때마다 UserDao의 생성자 메소드를 직접 수정하라고 하지 않고는 고객에게 자유로운 DB 커넥션 확장 기능을 가진 UserDao를 제공할 수 없다.
3. 관계설정 책임의 분리
기존에는 클라이언트가UserDao를 사용해야 할 입장이고, ConnectionMaker의 구현 클래스를 선택하고, 선택한 클래스의 오브젝트를 생성해서 UserDao와 연결해줄 수 있다. 즉 기존의 UserDao에서는 생성자에게 이 책임이 있다. 다른 관심사가 함께 있으니 확장성이 떨어지는 문제를 일으키는 것이다.
이 관심을 분리하여 클라이언트에 떠넘겨보자.
현재 UserDao 클래스의 main() 메소드가 UserDao클라이언트라고 볼 수 있다. 아예 UserDaoTest라는 클래스를 하나더 만들고 main()메소드를 UserDaoTest로 옮겨보자. 그리고 UserDao의 생성자를 수정해서 클라이언트가 미리 만들어둔 ConnectionMaker의 오브젝트를 전달 받을 수 있도록 파라미터를 하나 추가한다. UserDao의 생성자는 이제 리스트 1-11과 같이 바뀌었다. 클라이언트와 같은 제3의 오브젝트가 UserDao 오브젝트가 사용할 ConnectionMaker 오브젝트를 전달해주도록 만든 것이다.
public class UserDaoTest{
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ConnectionMaker connectionMaker = new DConnectionMaker();
// UserDao가 사용할 ConnectionMaker 구현 클래스를 결정하고 오브젝트를 만든다.
UserDao dao = new UserDao(connectionMaker);
// 1. UserDao 생성
// 2. 사용할 ConnectionMaker 타입의 오브젝트 제공, 결국 두 오브젝트 사이의 의존관계 설정
}
}
이렇게 인터페이스를 도입하고 클라이언트의 도움을 얻는 방법은 상속을 사용해 비슷한 시도를 했을 경우보다 유연하다. 인터페이스를 사용하면 다른 DAO클래스에도 ConnectionMaker의 구현클래스들을 그대로 적용할 수 있기 때문이다. DAO가 아무리 많아져도 DB 접속 방법에 대한 관심은 오직 한 군데에 집중되게 할 수 있고, DB 접속 방법을 변경해야 할 때도 오직 한곳의 코드만 수정하면 된다.