PengTory

[Spring] 오브젝트와 의존관계 (2) _ DAO의 확장 본문

Spring

[Spring] 오브젝트와 의존관계 (2) _ DAO의 확장

펭토리 2022. 10. 14. 00:08

이번 포스팅은 이전 포스팅 DAO의 분리와 이어진다.

앞서 두 개의 관심사에 따라서 오브젝트를 상속으로 분리했다. 그리고 우리는 상속이라는 방법을 사용한 것에 불편함을 느꼈다.

그렇다면 관심사에 따라 오브젝트를 분리하는 다른 방법으로는 무엇이 있을까? 클래스의 분리를 사용해보자.

 

클래스의 분리

이번에는 아예 상속관계도 아닌 완전히 독립적인 클래스를 만들어 볼 것이다.

DB 커넥션과 과려된 부분을 서브클래스가 아니라 아예 별도의 클래스에 담을 것이다.

UserDao는 상속을 통한 방법을 쓰지 않으니 더 이상 abstract일 필요는 없다.

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 클래스만 공급하고 상속을 통해 DB 커넥션 기능을 확장해서 사용하는게 불가능해졌다.

그렇다면 클래스를 분리하면서 이런 문제를 해결할 수 있을까? 인터페이스를 사용해보자.

 

인터페이스의 독립

두 개의 클래스를 연결은 하지만 기밀하게 연결되지 않도록 만들기 위한 방법에는 중간에 추상적이고 느슨한 연결고리를 만들어주는 것이 있다.

추상화란 어떤 것들의 공통적인 셩격을 뽑아내어 이를 따로 분리해내는 작업이다.

자바가 추상화를 위해 제공하는 가장 유용한 도구는 바로 인터페이스다.

인터페이스는 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다. 결국 오브젝트를 만들려면 구체적인 클래스 하나를 선택해야하겠지만 인터페이스로 추상해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들때 사용할 클래스가 무엇인지 몰라도 된다.

따라서 인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없다.

인터페이스는 어떤 일을 하겠다는 기능만 정의해 놓은 것이다. 따라서 인터페이스에는 어떻게 하겠다는 구현 방법은 나타나있지 않다.

 

위의 이론에 기반해 코드를 작성해보자. 

public interface ConnectionMaker {
    public Connection makeConnection()  throws ClassNotFoundException, SQLException;
}
public class DConectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws ClassNotFoundException, SQLException{
        // 독자적인 방법으로 Connection을 생성하는 코드
    }
}
public class UserDao {
    private ConnectionMaker connectionMaker;
    
    public UserDao(){
        connectionMaker = new DConectionMaker();
    }
    
    public void add(User user) throws ClassNotFoundException, SQLException{
        Connection c = connectionMaker.makeConnection();
        // ...
    }
    
    public User get(String id) throws ClassNotFoundException, SQLException{
        Connection c = connectionMaker.makeConnection();
        // ...
    }
}

다음과 같이 코드를 수정하면 DB 접속용 클래스를 다시 만든다 해도 UserDao의 코드를 수정할 일은 없어 보인다.

그러나 UserDao 코드를 살펴보면 DConnection라는 클래스 이름이 보인다. 

이렇게 되면 UserDao안에 분리되지 않은 또 다른 관심사항이 존재하기 때문에 여전히 UserDao의 변경 없이는 DB 커넥션의 기능이 자유롭지 못해진다.

해결방법으로 일단은 클라이언트(main)에 넘겨보자. 

public UserDao(ConnectionMaker connectionMaker){
    this.connectionMaker = connectionMaker;
}
public class UserDaoTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException{
        ConnectionMaker connectionMaker = new DConectionMaker();

        UserDao dao = new UserDao(connectionMaker);
        // ...
    }
}

생성자를 수정하고 UserDao 클라이언트인 main()메소드 등에 관계설정 책임을 추가했다.

클라이언트인 UserDaoTest가 어느 ConnectionMaker를 사용할지에 대한 책임을 받은 덕분에 UserDao는 자신의 관심사이자 책임인 사용자 데이터 엑세스 작업을 위해 SQL을 생성하고, 이를 실행하는 데만 집중할 수 있게 됐다.

아래는 인터페이스를 사용한 방식으로 변경한 관계 및 구조이다.

이를 통해 앞에서 사용한 상속을 통한 확장 방법보다 더 깔금하고 유연한 방법으로 UserDao와 ConnectionMaker 클래스들을 분리하고, 서로 영향을 주지 않으면서도 필요에 따라 자유롭게 확장할 수 있는 구조가 됐다.

 

이론적 설명 추가

지금까지 이론적으로 공부하며 나온 용어들을 정리해보자.

1) 개방 폐쇄 원칙: 클래스나 모듈은 확장에는 열려있어야 하고 변경에는 닫혀있어야 한다.

2) 높은 응집도: 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어있다.

3) 낮은 결합도: 하나의 변경이 발생할 때 마치 파문이 이는 거서럼 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태

4) 전략패턴: 자신의 기능 맥락에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴

 

[참고자료] 토비의 스프링 3.1 Vol.1 스프링의 이해와 원리