개요
1. 분리의 방법3 - 클래스의 분리
2. 분리의 방법4 - 인터페이스의 도입
3. 관계설정 책임의 분리
본문
1. 분리의 방법3 - 클래스의 분리
앞서 상속을 통해서 서브 클래스로 관심을 분리하는 분리 방법을 적용해보았고 그에 따른 문제점도 언급해 보았다. 슈퍼 클래스와 서브 클래스가 생각보다 긴밀한 관계이기에 현재 책에서 분리한 커넥션과 UserDao를 슈퍼 클래스와 서브 클래스로 분리한 것은 그다지 적합하지 못하다. 따라서 화끈하게 분리할 수 있도록 독립된 클래스로 분리하는 작업을 해보겠다.
package springbook.user.ex4.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;
//DAO의 확장-클래스의 분리
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker = new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c =simpleConnectionMaker.makeNewConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c =simpleConnectionMaker.makeNewConnection();
...
}
}
package springbook.user.ex4.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
//메소드 단계에서 분리하는 걸 넘어서 클래스로 따로 분리
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/springbook?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=UTF8&", "root", "1234");
return c;
}
}
완전히 독립된 클래스로 분리되었기에 슈퍼-서브 클래스 때와 달리 슈퍼 클래스의 변화가 서브 클래스에게 영향을 준다든지 하는 일이 없어졌다. 하지만 이렇게 되면 D사와 N사의 요청에 따라 다른 커넥션을 제공하는 게 다시 불가능해졌다. D사와 N사의 요청에 따라 독립 클래스 SimpleConnectionMaker에 각각의 메소드를 만든다고 하더라도 UserDao에서 그 메소드를 찾기 위해서 UserDao의 소스가 수정되어야만 하기 때문이다. 이러한 문제를 해결하기 위해서 인터페이스를 도입한다.
2. 분리의 방법4 - 인터페이스의 도입
인터페이스는 두 클래스를 연결하는 추상적인 느슨한 고리라고 생각하면 된다. 인터페이스는 구현되지 않은 메소드를 지니고 있고 이 메소드를 실질적인 구현 클래스에서 구현하도록 한다. N사와 D사의 요구 사항은 인터페이스의 미구현 메소드를 구현하면서 완성된다. 메소드의 이름은 인터페이스의 미구현 메소드의 이름과 같도록 강제되기에 UserDao에서 N사와 D사의 경우의 수에 따라서 다른 메소드를 요청할 필요가 없어진다.
package springbook.user.ex5.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;
//DAO의 확장-인터페이스의 도입
public class UserDao {
private ConnectionMaker connectionMaker;
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();
...
}
}
package springbook.user.ex5.dao;
import java.sql.Connection;
import java.sql.SQLException;
//인터페이스 도입
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
package springbook.user.ex5.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection c = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/springbook?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Seoul&useUnicode=true&characterEncoding=UTF8&", "root", "1234");
return c;
}
}
하지만 UserDao의 'connectionMaker = new DConnectionMaker();'라는 코드에서 확인할 수 있듯이 관계 설정의 문제가 UserDao의 소스에 남아있다. N사냐 D사에 따라서 UserDao의 코드에 손을 대어야 하는 문제는 여전하다. 이 문제도 결국 관심사의 분리가 제대로 이루어지지 않았기에 생기는 문제이다. UserDao에서 어떤 회사와 관계를 가질지에 대한 관심을 가지는 것은 UserDao의 다른 관심사와 동떨어져 있는 관심사이다. 따라서 이를 분리시킬 필요가 있다. UserDao가 어떤 회사와 관계를 가질 것인가 하는 관심사는 이 프로그램을 사용하는 주체, 즉 클라이언트의 관심사이다. 현재 클라이언트의 역할을 하는 것은 test시 작동하는 main() 메소드이다. 따라서 메인 메소드에서 UserDao의 생성하면서 생성자 매개변수를 통해 main() 메소드에서 결정한 회사를 주입할 필요가 있다. 이를 코드화 시키면 아래와 같다.
package springbook.user.ex6.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import springbook.user.domain.User;
//DAO의 확장-인터페이스의 도입
public class UserDao {
private ConnectionMaker connectionMaker;
//생성자 매개 변수를 이용해 클라이언트에게 관계 맺는 책임 넘기기
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public void add(User user) throws ClassNotFoundException, SQLException{
Connection c =connectionMaker.makeConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection c =connectionMaker.makeConnection();
...
}
}
public class UserDaoTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao dao = new UserDao(connectionMaker);
...
}
}
위와 같이 정리하여 UserDao에서 DConnectionMaker를 완전히 분리해내는 데에 성공했다. 이제 테스트 클래스의 선택에 따라서 원하는 대로 구현된 connectionMaker를 UserDao에 보내고 작동할 수 있게끔 만들어졌다.
정리
여태까지 초난감 DAO코드를 만지면서 알 수 있는 이론적인 내용을 간략히 다뤄보고자 한다.
개방폐쇄 원칙 - 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다.
최초에 한 클래스에 모든 기능을 몰아넣은 초난감DAO의 경우 D사 N사의 요구에 따라서 클래스 전체를 훑어보고 이해하고, 커넥션을 맡은 부분을 각각의 메소드에서 전부 수정해야만 했다. 이는 확장하기도 불편한 것이며 모든 것이 변화해야 하니 변화에 크게 열려있는 불안정한 코드이다. 반대로 현재 인터페이스를 활용한 코드는 인터페이스를 통해서 느슨한 연결이 되어 있기에 인터페이스와 연결하여 A커넥션, B커넥션 .. Z커넥션까지 전부 다 달아도 DAO의 코드는 건드릴 필요가 없다. 이런 모습이 개방폐쇄의 원칙을 잘 보여준다.
높은 응집도와 낮은 결합도 - 높은 응집도란 클래스 하나의 책임 또는 관심사에만 집중되어 있다는 것을 의미한다. 최초 초난감 DAO는 DB커넥션을 만드는 기능이 분리되어 있지 않았고 이에 따라 커넥션을 기능을 바꾸고 싶을 뿐인데 DAO 내부의 코드를 건드려야 하는 번거로운 일이 일어났다. 이는 DAO전체 기능에 영향을 줄 수도 있어 위험을 초래한다. 반면에 관심사를 분리하여 독립 클래스로 다룬 경우 해당 클래스만 수정해주면 되고 인터페이스를 통한 느슨한 연결은 DAO의 코드를 전혀 건드릴 필요가 없게 만들어 안전하고 견고한 코드를 만들 수 있게 해준다. 이때 느슨한 연결이 낮은 결합도이다.
전략 패턴-자신의 기능 맥락에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고 이를 구현한 구체적인 알고리즘을 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴이다. 책을 따라 만들었던 USerDaoTest-UserDao-ConnectionMaker 구조가 전략패턴의 예시이다. UserDao를 전략패턴의 맥락(컨텍스트)로 삼아서 컨텍스트 자신의 기능을 수행하는데 필요한 기능 중 변경 가능한 DB연결 방식을 ConnectionMaker라는 인터페이스로 정의하고 이를 구현한 클래스(전략)을 바꿔가면서 사용할 수 있게 분리했다. 또한 UserDaoTest와 같은 클라이언트가 컨텍스트(UserDao)를 사용하면서 구현 클래스(전략)을 선택하는 것도 구현되어 있어 전략 패턴의 훌륭한 예시이다.
소스 깃허브
https://github.com/cholongbul/Tobyspring
cholongbul/Tobyspring
토비의 스프링 연습. Contribute to cholongbul/Tobyspring development by creating an account on GitHub.
github.com
'스프링' 카테고리의 다른 글
토비의 스프링 - 1.5 스프링IoC (0) | 2021.05.20 |
---|---|
토비의 스프링 - 1.4 제어의 역전 (0) | 2021.05.20 |
토비의 스프링 - 1.2 DAO의 분리 (0) | 2021.05.19 |
토비의 스프링 - 1.1 초난감 DAO (0) | 2021.05.19 |
테스트 입문 (0) | 2021.04.08 |