Spring/Toby's Spring

[토비의 스프링 3.1 Vol. 1][1장. 오브젝트와 의존관계] 2절. DAO의 분리

나말지 2024. 12. 8. 15:03

앞서 클래스 상속을 통해 관심사 분리를 했었다.

이번에는 서브 클래스가 아닌 아예 독립된 클래스로 분리해보자.

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 로 바꾸고 싶다면 클라이언트 코드에서 한 줄만 수정해주면 된다.

이렇게 인터페이스를 도입하고 클라이언트의 도움을 받는 방법은 상속을 사용한 방식에 비해 훨씬 유연하다.