1. 오브젝트와 의존관계 - DAO의 확장


1.3 DAO의 확장

이전 장에서 "객체지향 세계에서는 비즈니스 요구사항에 따라서 오브젝트에 대한 설계가 변할 수 있다." 라고 했다.

그런데 여러 오브젝트들은 서로 다른 변화의 성격을 갖고 있다.
변화의 성격이 다르다는 건 변화의 이유와 시기, 주기 등이 다르다는 뜻이다.

관심사를 분리하여 코드를 작성하는 이유
변화의 성격이 다른 것을 분리해서, 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서다.

1.3.1 클래스의 분리

이전 장에서 상속을 통한 관심사 분리의 문제점에 대하여 다루었다.

이번에는 관심사가 다르고 변화의 성격이 다른 코드를 완전히 독립적인 클래스로 만들어서 분리할 것이다.

방법

  1. DB 커넥션과 관련된 부분을 서브클래스가 아닌, 독립적인 클래스로 만든다.
  2. 그 후 UserDao가 해당 클래스를 사용하도록 만들면 된다.

public class UserDao {

    private SimpleConnectionMaker simpleConnectionMaker;

	// 상태를 관리하는 것도 아니닌 한번만 만들어 인스턴스 변수에 저장해두고 메소드에서 사용하게 한다.
    public UserDao() {
        this.simpleConnectionMaker = new SimpleConnectionMaker();
    }
    
    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection c = this.simpleConnectionMaker.makeNewConnection();
    ...
    }

    public void get(String id) throws ClassNotFoundException, SQLException {
        Connection c = this.simpleConnectionMaker.makeNewConnection();
    ...
    }

}

DB 연결에 대한 관심사를 독립시킨 SimpleConnectionMaker는 아래와 같이 만든다.

public class SimpleConnectionMaker {
    public Connection getConnection() throws ClassNotFoundException, SQLException {

        Class.forName("com.mysql.cj.jdbc.Driver");

        // Connection 을 가져온다.
        Connection c = DriverManager.makeNewConnection(
                "jdbc:mysql://localhost:3306/toby?useSSL=false", "root", "password"
        );

        return c;

    }
}

위와 같이 코드를 리팩토링 한 후에 테스트를 진행하여 기능에 변화가 없다는 것 을 검증하여야 한다.
main()메소드를 다시 실행해서 수정하기 전과 동일한 결과가 나오는지 확인해보자.


변화의 성격이 다른 코드를 독립된 클래스를 생성하여 분리한 것은 잘한 것 같다.
하지만, N 사와 D 사에 UserDao 클래스만 공급하고 상속을 통해 DB 연결 시능을 확장해서 사용하게 했던 게 다시 불가능해졌다.

  • UserDao의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있기 때문이다.

즉 클래스를 분리한 경우에도 상속을 이용했을 때와 마찬가지로 자유로운 확장이 가능하게 하려면 2가지 문제를 해결해야 한다.

  1. 각 클라이언트가 DB 연결을 가져오는 메소드의 이름을 다르게 했을때 발생하는 문제

    • 우리는 makeNewConnection()이라는 메소드를 사용했지만, 클라이언트는 서로 다른 메소드 이름을 사용할 수 있다.
    • 이렇게 되면, makeNewConnection()이 쓰인 모든 코드를 수정해주어야 한다.
  2. DB연결을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고 있어야 한다.

1.3.2 인터페이스의 도입

위 문제의 가장 좋은 해결책은 두개의 클래스(UserDao & SimpleConnectionMaker)가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어 주는 것이다.

인터페이스

  • 자바가 추상화를 위해 제공하는 가장 유용한 도구
  • 자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춰버린다.

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

아래 그림은 인터페이스를 도입한 후 클래스의 관계를 표현한 것이다.

ConnectionMaker 인터페이스

public interface ConnectionMaker {
    public Connection makeNewConnection() throws ClassNotFoundException, SQLException;
}

ConnectionMaker 구현 클래스

public class DConnectionMaker implements ConnectionMaker {
    public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
    	// D 사의 독자적인 방법으로 Connection을 생성하는 코드
	}
}

ConnectionMaker 인터페이스를 사용하도록 개선한 UserDao

public class UserDao {
	
    // 인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다.
    private ConnectionMaker connectionMaker;

    public UserDao() {
    	// 하지만! 여기에는 특정 클래스 이름이 나온다.
        this.connectionMake = new DConnectionMaker();
    }
    
    public void add(User user) throws ClassNotFoundException, SQLException {
        Connection c = this.connectionMaker.makeNewConnection();
    ...
    }

    public void get(String id) throws ClassNotFoundException, SQLException {
        Connection c = this.connectionMaker.makeNewConnection();
    ...
    }

}

UserDao의 add(), get() 메소드와 필드에는 ConnectionMaker라는 인터페이스와 인터페이스의 메소드인 makeNewConnection() 만 사용하도록 했다.
그러므로 N 사와 D 사가 DB 연결 클래스를 다시 만든다고 해도, UserDao의 코드를 고칠 필요가 없을 것 같다.

하지만, UserDao의 코드를 자세히 보면 DConnectionMaker 라는 클래스 이름이 보인다.
여전히 UserDao에 특정 클라이언트에 대한 정보를 설정해 줘야하는 코드가 남아있다.

1.3.3 관계설정 책임의 분리

여전히 UserDao에는 어떤 ConnectionMaker 구현 클래스를 사용할지를 결정하는 코드가 남아있다.

그 이유는 UserDao 안에 분리되지 않은, 또 다른 관심사항이 존재하고 있기 때문이다.

  • UserDao와 UserDao가 사용할 ConnectionMaker의 특정 구현 클래스 사이의 관계를 설정해주는 것에 관한 관심

이 관심사를 UserDao에서 분리하지 않으면 UserDao는 독립적으로 확장 가능한 클래스가 될 수 없다.

해결법

  • UserDao의 클라이언트 오브젝트가 해당 관심사를 분리하여 두기에 적절한 곳 이다.
  • UserDao의 클라이언트에서 UserDao를 사용하기 전에, 먼저 UserDao가 어떤 ConnectionMaker의 구현 클래스를 사용할지를 결정하도록 만든다.

즉 클래스 사이에 관계를 만들어지는 것이 아닌. 오브젝트 사이에 다이내믹한 관계가 만들어지는 것이다.

클라스 사이의 관계 : 코드에 다른 클래스 이름이 나타나기 때문에 만들어지는 것

오브젝트 사이의 관계 : 코드에서 특정 클래스의 이름을 몰라도, 해당 클래스의 오브젝트를 인터페이스 타입으로 받아서 사용할 수 있다.
객체지향 프로그램의 다형성이라는 특징 덕분이다.

오브젝트 사이의 관계는 런타임 시점의 오브젝트 사이에 생기는 관계이다.
의존관계 또는 런타임 사용관계, 링크라고 불린다.

UserDao의 클라이언트 : main()
UserDao의 생성자를 수정해서 클라이언트가 미리 만들어둔 특정 클래스인 ConnectionMaker의 오브젝트를 전달 받을 수 있도록 파라미터를 하나 추가한다.

수정한 생성자

public class UserDao {
	
    // 인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다.
    private ConnectionMaker connectionMaker;

    public UserDao(ConnectionMaker connectionMaker) {
        this.connectionMake = connectionMaker
    }
}

관계설정 책임이 추가된 UserDao 클라이언트인 main( ) 메소드

public class main {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
    	// UserDao가 사용할 ConnectionMaker 구현 클래스를 결정하고 오브젝트를 만든다.
        ConnectionMaker connectionMaker = new DConnectionMaker();

		// 1. UserDao 생성
        // 2. 사용할 ConnectionMaker 타입의 오브젝트 제공.
        //    결국 두 오브젝트 사이의 의존관계 설정
        UserDao userDao = new UserDao(connectionMaker);
	}
}

UserDao의 클라이언트인 main은 UserDao의 변경없이 N 사와 D 사가 자신들을 위한 DB 연결 클래스를 만들어서 사용할 수 있게 되었다.

1.3.4 원칙과 패턴

지금까지 초난감 DAO 코드를 개선해온 결과를 객체지향 기술의 여러 가지 이론을 통해 설명 하려고 한다.

개방 폐쇄 원칙OCP, Opean-Closed Principle

  • 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀있어야 한다
  • UserDao는 DB연결 방법이라는 기능을 확장하는 데는 열려 있다.
    UserDao에 전혀 영향을 주지 않고도 기능을 확장할 수 있게 되어 있다.
  • UserDao 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지않고 유지할 수 있으므로 변경에는 닫혀있다.

높은 응집도와 낮은 결합도high coherence and low coupling

높은 응집도

  • 하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 뜻
  • 변화가 일어날 때, 해당 모듈(같은 관심사를 갖는 곳)에서 변하는 부분이 크다는 것

낮은 결합도

  • 책임과 관심사가 다른 오브젝트 도는 모듈과는 낮은 결합도를 유지하는 것이 바람직하다.
  • 낮은 결합도는 관계를 유지하는 데 꼭 필요한 최소한의 방법만 간접적인 형태로 제공
    나머지는 서로 독립적으로 만들어주는 것이다.

전략 패턴Strategy Pattern

  • 개방 폐쇄 원칙에 가장 잘 맞는 패턴이다.
  • 자신의 기능에서, 필요에 따라 변경이 필요한 기능을 인터페이스를 통해 통째로 외부로 분리시키고,
    이를 구현한 구체적인 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다.

소스코드 : github

좋은 웹페이지 즐겨찾기