책임의 분리
토비의 스프링을 공부하다보면 계속 나오는 단어가 보이는데
그것이 바로 관심, 책임이다.
관심이란 무엇이고, 책임은 무엇이기에
계속해서 나눠야하는 것이며 분리시켜주어야하는 것일까?
비전공자, 개발 초보의 입장에서는 계속해서 물음표가 이어졌다.
토비의 스프링 책을 보면서 처음부터 다시 정리해보자.
1. 우리는 맨 처음 UserDao에 직접 getConnection이라는 메서드를 만들어 DB와 연결을 시도했다.
public class UserDao {
/*중복된 코드를 독립적인 메소드로 만들어 중복 제거*/
private Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/tobi", "root", "1234");
return c;
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = getConnection(); // DB 연결이 필요할 때, getConnection()메소드 이용
...
}
}
UserDao는 DB와 관련된 메서드도 진행하며, DB와 연결하는 메서드를 진행하면서 굉장히 많은 일을 담당하고 있었다.
2. 확장성을 고려하여 인터페이스를 사용하며, 최소한의 통로를 통해 접근하는 쪽에서 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 사용할 수 있도록 설계를 수정했다.
추상화를 이용한 방법도 있었지만, 해당 방법은 상속을 통한 방법으로 단점을 가지고 있으므로 생략하자.
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException, SQLException;
}
public class ChordpliConnectionMaker implements ConnectionMaker {
@Override
public Connection makeConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/tobi", "root", "1234");
return c;
}
}
Connection 인터페이스를 구현한 클래스를 만들었고, 본인이 원하는 DB 커넥션을 만들고 가져오도록 메소드를 작성하였다.
public class UserDao {
private ConnectionMaker connectionMaker; // 인터페이스를 통해 오브젝트에 접근하므로 구체적인 클래스 정보를 알 필요가 없다.
public UserDao(){
connectionMaker = new ChordpliConnectionMaker(); // -> 하지만 여기서 클래스 이름이 나와버린다. !!!
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection c = connectionMaker.makeConnection(); // 인터페이스에 정의된 메소드 사용,
//클래스가 바뀐다고 메소드 이름이 변경될 걱정은 없다.
...
}
}
인터페이스를 사용하면서 구체적인 정보를 제거했지만, 어떤 클래스의 오브젝트를 사용할지 결정하는 생성자의 코드가 제거되지 않았다.
해당 코드의 주석을 보면 하지만 여기서 클래스의 이름이 나와버린다 이 말이 핵심 단어중 하나라고 나는 생각한다.
수 많은 군중속에서 이름이 불리는 순간, 모두의 관심이 Chordpli에게 가버린 것이다.
3. 관계설정 책임의 분리
책에서는 "왜 UserDao가 인터페이스 뿐 아니라 구체적인 클래스까지 알아야 하는 문제"라고 표현하고 있다.
UserDao에는 어떤 ConnectionMaker 클래스를 사용할지 결정하는 코드가 남아있으며 이 때문에 UserDao안에 분리되지 않은, 또 다른 관심사항이 존재하며 UserDao 변경없이 DB커넥션 기능의 확장이 자유롭지 못하다라고 표현하고 있다.
즉 필자가 원하는 바는 UserDao의 코드 수정을 바라지 않는다는 것이다.
이름이 나와버리면 Chordpli DB가 아닌 Likelion DB의 연결을 바랄때, 클래스의 수정이 필수적이기 때문이다.
new Chordpli()는 UserDao가 어떤 ConnectionMaker 구현 클래스의 오브젝트를 이용하게 할지 결정하게 만드는, 그 자체로 충분히 독립적인 관심사를 담고 있는 것이다.
즉, UserDao와 UserDao가 사용할 ConnectionMaker의 특정 구현 클래스 사이의 관계를 설정해주는 것에 대한 관심인 것이다.
그래서 필자가 선택한 방법은
오브젝트 서비스, 사용하는 오브젝트 클라이언트에게 이러한 관심사를 던져버리는 방법이다.
UserDao의 모든 코드는 ConnectionMaker 인터페이스 이외에는 어떤 클래스와도 관계를 가져서 안되기 때문이다.
물론 UserDao 오브젝트가 동작하려면 특정 클래스의 오브젝트와 관계를 맺어야 한다. 하지만 클래스 사이에 관계가 만들어지는 것은 아니고, 단지 오브젝트 사이에 다이나믹한 관계가 생기는 것, 이 차이를 구분해야한다.
2번에서 문제가 되었던 생성자를 이제 수정해보자
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao(ConnectionMaker connectionMaker){
this.connectionMaker = connectionMaker();
}
}
생성자에 파라미터를 생성한 다음, 해당 connectionMaker의 관심사를 클라이언트에게 넘겨버리는 것이다.
class UserDaoTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
ConnectionMaker connectionMaker = new LikelionConnectionMaker(); // userDao가 사용할 ConnectionMaker구현 클래스를 결정하고 오브젝트 생성
UserDao userDao = new UserDao(connectionMaker);
}
}
UserDaoTest는 UserDao와 ConnectionMaker 구현 클래스와의 런타임 오브젝트 의존 관계를 설정하는 책임을 담당한다.
그래서 특정 ConnectionMaker 구현 클래스의 오브젝트를 만들고, UserDao 생성자 파라미터에 넣어 두 개의 오브젝트를 연결해준다.
이것으로 UserDao에 있으면 안 되는 다른 관심사항, 책임을 클라이언트로 떠넘기는 작업이 끝이 난다.
정리
토비의 책을 읽고, 계속해서 코드를 쓰고 왔다갔다 하면서 조금씩 이해가 되는 것 같다.
물론 개인적인 견해이므로 댓글로 좋은 의견을 달아주시면 검토하고, 수정하고, 보충할 예정이다.
각 클래스에는 각 클래스의 역할을 가지고 있으며, 그 역할에 대한 기능만을 수행해야 한다.
해야하는 역할이 늘어날수록 그 해당 클래스는 다른 관심이 생기기 시작하고, 확장성이 떨어지고, 유지보수에도 문제가 생길 수 있다.
그럼 관심이란 무엇인가.
클래스명이 보일수록 관계가 밀접해질 수 있다.
이 클래스명이라는 것이 명확하지 않을 수 있으나, 해당 교본에 따르면 인터페이스명은 괜찮고 그걸 상속하는 클래스 이름은 안되는 것 같다.
클래스 명이 명확하다는 것은 변경이 있을때마다 해당 클래스 이름을 모두 변경해야하므로 수정이 힘들어 질 수 있기 때문이다.
클라이언트(사용자)에게 떠넘길 수 있는 부분은 최대한 떠넘기고, 관심사를 분리시킬 수 있도록 하자.
'Server > Spring&Spring Boot' 카테고리의 다른 글
[테스트 코드] Controller Test, MockMVC (0) | 2022.11.18 |
---|---|
[토비의 스프링] 싱글톤 레지스트리와 오브젝트 스코프 (0) | 2022.11.12 |
[토비의 스프링] 스프링의 IoC - 애플리케이션 컨텍스트의 동작 방식 (0) | 2022.10.29 |
[토비의 스프링] 스프링의 IoC - 오브젝트 팩토리를 이용한 스프링 IoC (0) | 2022.10.29 |
[토비의 스프링] 제어의 역전 - 오브젝트 팩토리 활용, 제어권의 이전을 통한 제어관계 역전 (0) | 2022.10.29 |