공부해봅시당
[스프링] 오브젝트와 의존관계 3 본문
1.3. DAO의 확장
모든 오브젝트는 변함
하지만 오브젝트다 다 동일한 방식으로 변하는 건 아님
관심사에 따라서 분리한 오브젝트들은 제각기 독특한 변화의 특징이 있음
지금까지 데이터 액세스 로직을 어떻게 만들 것인가와 DB 연결을 어떤 방법으로 할 것인가라는 두 개의 관심을 상하위 클래스로 분리시킴
이 두 개의 관심은 변화의 성격이 다름
변화의 성격이 다르다는 말의 의미 > 변화의 이유와 시기, 주기 등이 다르다는 뜻
추상 클래스를 만들고 이를 상속한 서브클래스에서 변화가 필요한 부분을 바꿔서 쓸 수 있게 만든 이유는 이렇게 변화의 성격이 다른 것을 분리해서, 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위함
하지만 앞서 살펴본 것처럼 상속이라는 방법을 사용했다는 사실이 불편하게 느껴짐
1.3.1. 클래스의 분리
이번에는 관심사가 다르고 변화의 성격이 다르 이 두 가지 코드를 좀 더 화끈하게 분리해보자!
두 개의 관심사를 본격적으로 독립시키면서 동시에 손쉽게 확장할 수 있는 방법은?
지금까지는 성격이 다른, 그래서 다르게 변할 수 있는 관심사를 분리하는 작업을 점진적으로 진행함
처음에는 독립된 메소드를 만들어서 분리했고, 다음에는 상하위 클래스로 분리함
이번에는 아예 상속관계도 아닌 완전히 독립적인 클래스로 만들어 보겠음
DB 커넥션과 관련된 부분을 서브클래스가 아니라, 아예 별도의 클래스에 담음
그리고 이렇게 만든 클래스를 UserDao가 이용하게 하면 됨
위와 같이 SimpleConnectionMaker라는 새로운 클래스를 만들고 DB 생성 기능을 그 안에 넣음
그리고 UserDao는 new 라는 키워드를 사용해 SimpleConnectionMaker 클래스의 오브젝트를 만들어두고, 이를 add(), get() 메소드에서 사용하면 됨
각 메소드에서 매번 SimpleConnectionMaker의 오브젝트를 만들수도 있지만 그보다는 한 번만 SimpleConnectionMaker 오브젝트를 만들어서 저장해두고 이를 계속 사용하는 편이 나음
UserDao.java
public class UserDao {
private SimpleConnectionMaker simpleConnectionMaker;
public UserDao() {
simpleConnectionMaker - new SimpleConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = simpleConnectionMaker.makeNewConnection();
...
}
public void get(String id) throws ClassNotFoundException, SQLException {
Connection conn = simpleConnectionMaker.makeNewConnection();
...
}
}
SimpleConnectionMaker.java
package springbook.user.dao;
...
public class SimpleConnectionMaker {
public Connection makeNewConnection() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConenction("jdbc:mysql://localhost/springbook", "spring", "book");
return conn;
}
}
기존 코드에 많은 수정을 했지만 기능에 변화를 준 것은 없음
단지 내부 설계를 변경해서 좀 더 나은 코드로 개선한 것
코드를 수정했기 때문에 다시 한 번 테스팅 필요(테스팅 전 기존 데이터 삭제 필수)
분리는 잘 했지만 다른 문제 발생
N사와 D 사에 UserDao 클래스만 공급하고 상속을 통해 DB 커넥션 기능을 확장해서 사용하게 했던게 다시 불가능해짐
UserDao의 코드가 SimpleConnectionMaker라는 특정 클래스에 종속되어 있기 때문에 상속 사용 시처럼 UserDao 코드의 수정 없이 DB 커넥션 생성 기능 변경 방법 없음
다른 방식으로 DB 커넥션을 제공하는 클래스 사용을 위해 UserDao 소스코드의 다음 줄을 직접 수정해야 함
UserDao의 소스코드를 함께 제공하지 않고는 DB 연결 방법을 바꿀 수 없다는 처음 문제로 다시 되돌아옴
simpleConnectionMaker = new SimpleConnectionMaker();
이렇게 클래스 분리한 경우에도 상속을 이용했을 때와 마찬가지로 자유로운 확장이 가능하게 하려면 두 가지 문제 해결 필요
1. SimpleConnectionMaker의 메소드가 문제
makeNewConnection()을 사용해 DB 커넥션을 가져오세 했는데, 만약 D사에서 만든 DB 커넥션 제공 클래스는 openConnection()이라는 메소드 이름을 사용했다면 UserDao 내에 있는 add(), get() 메소드의 커넥션을 가져오는 코드를 다음과 같이 일일이 변경해야 함
Connection conn = simpleConnectionMaker.openConnection();
2. DB 커넥션을 제공하는 클래스가 어떤 것인지를 UserDao가 구체적으로 알고 있어야 함
UserDao에 SimpleConnectionMaker라는 클래스 타입의 인스턴스 변수까지 정의해놓고 있으니, N사에서 다른 클래스를 구현하면 어쩔 수 없이 UserDao 자체를 다시 수정해야 함
이런 문제의 근본적인 원인은 UserDao가 바뀔 수 있는 정보, 즉 DB 커넥션을 가져오는 클래스에 대해 너무 많이 알고 있기 때문
어떤 클래스가 쓰일지, 그 클래스에서 커넥션을 가져오는 메소드는 이름이 뭔지까지 일일이 알고 있어야 함
따라서 UserDao는 DB 커넥션을 가져오는 구체적인 방법에 종속됨
상속을 이용한 방법만도 못한게 아닌가 싶을 정도...!
1.3.2. 인터페이스 도입
클래스를 분리하면서도 이런 문제를 해결할 수 있는 방법은?
가장 좋은 해결책은 두 개의 클래스가 서로 긴밀하게 연결되어 있지 않도록 중간에 추상적인 느슨한 연결고리를 만들어주는 것
추상화
어떤 것들의 공통적인 성격을 뽑아내 이를 따로 분리해내는 작업
자바가 추상화를 위해 제공하는 가장 유용한 도구는 인터페이스
인터페이스
자신을 구현한 클래스에 대한 구체적인 정보는 모두 감춤
오브젝트를 만들렴녀 구체적인 클래스 하나를 선택해야겠지만 인터페이스로 추상화해놓은 최소한의 통로를 통해 접근하는 쪽에서는 오브젝트를 만들 때 사용할 클래스가 무엇인지 몰라도 됨
인터페이스를 통해 접근하게 하면 실제 구현 클래스를 바꿔도 신경 쓸 일이 없음
인터페이스를 도입하면 UserDao는 자신이 사용할 클래스가 어떤 것인지 몰라도 됨
인터페이스를 통해 원하는 기능을 사용하기만 하면 됨
ConnectionMaker.interface
package springbook.user.dao;
...
public interface ConnectionMaker {
public Connection makeConnection() throws ClassNotFoundException. SQLException;
}
DConnectionMaker.java
package spring.user.dao;
...
public class DConnectionMaker implements ConnectionMaekr {
...
public Connection makeConnection() throws ClassNotFoundException, SQLException {
// D 사의 독자적인 방법으로 Connection을 생성하는 코드
}
}
UserDao.java
public class UserDao {
private ConnectionMaker connectionMaker;
public UserDao() {
connectionMaker = new DConnectionMaker();
}
public void add(User user) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
...
}
public User get(String id) throws ClassNotFoundException, SQLException {
Connection conn = connectionMaker.makeConnection();
...
}
}
위 코드로 수정해도 여전히 문제가 남아있다.
UserDao 코드 중 DConnection이라는 클래스 이름이 보인다.
DConnection 클래스의 생성자를 호출해서 오브젝트를 생성하는 코드가 다음과 같이 여전히 UserDao에 남아 있다.
connectionMaker = new DConnectionMaker();
다시 원점이 된 듯하다.
1.3.3. 관계설정 책임의 분리
UserDao와 ConnectionMaker라는 두 개의 관심을 인터페이스까지 써가면서 거의 완벽하게 분리했는데도, 왜 UserDao가 인터페이스뿐 아니라 구체적인 클래스까지 알아야 한다는 문제가 발생하는 것일까?
그 이유는 UserDao 안에 분리되지 않은 또 다른 관심사항이 존재하고 있기 때문이다.
바로 UserDao가 어떤 ConnectionMaker 구현 클래스의 오브젝트를 이용하게 할지를 결정하는 것이다.
간단히 말자하면 UserDao와 UserDao가 사용할 ConnectionMaker의 특정 구현 클래스 사이의 관계를 설정해주는 것에 관한 관심이다.
UserDao를 사용하는 클라이언트가 적어도 하나는 존재할 것이다.
여기서 말하는 클라이언트는 거창하게 브라우저나 PC 같은 클라이언트 장비를 말하는게 아니다.
두 개의 오브젝트가 있고 한 오브젝트가 다르 오브젝트의 기능을 사용한다면, 사용되는 쪽이 사용하는 쪽에게 서비스를 제공하는 셈이다
따라서 사용되는 오브젝트를 서비스, 사용하는 오브젝트를 클라이언트라고 부를 수 있다.
여기서 "UserDao의 클라이언트 = UserDao를 사용하는 오브젝트"이다
바로 이 UserDao의 클라이언트 오브젝트가 바로 제3의 관심사항인 UserDao와 ConnectionMaker 구현 클래스의 관계를 결정해주는 기능을 분리해서 두기에 적절한 곳이다.
즉, UserDao 오브젝트와 특정 클래스로부터 만들어진 ConnectionMaker 오브젝트 사이에 관계를 설정해주는 것이 해결책!
클래스 사이에 관계가 만들어진다는 것
한 클래스가 인터페이스 없이 다른 클래스를 직접 사용한다는 뜻
오브젝트 사이의 관계는 런타임 시에 한쪽이 다른 오브젝트의 레퍼런스를 갖고 있는 방식으로 만들어짐
EX) DConnectionMaker의 오브젝트의 레퍼런스를 UserDao의 connectionMaker 변수에 넣어서 사용하게 함으로써 이 두 개의 오브젝트가 '사용'이라는 관계를 맺게 해줌
오브젝트 사이의 관계가 만들어지려면 일단 만들어진 오브젝트가 있어야 함
< 방법 >
1. 직접 생성자를 호출해서 직접 오브젝트를 만드는 방법
2. 외부에서 만들어준 것을 가져오는 방법
2번 방법 사용 시, 메소드 파라미터나 생성자 파라미터를 이용하면 됨
UserDao의 모든 코드는 ConnectionMaker 인터페이스 외에는 어떤 클래스와도 관계를 가져서는 안되게 해야함
그래야 위의 구조처럼 불필요한 의존관계를 가지지 않을 수 있음
UserDao 오브젝트가 DConnectionManager 오브젝트를 사용하게 하려면 두 클래스의 오브젝트 사이에 런타임 사용관계 또는 링크, 또는 의존관계라고 불리는 관계를 맺어주면 됨
위 다이어그램은 모델링 시점의 클래스 다이어그램이 아니라 런타임 시점의 오브젝트 간 관계를 나타내는 오브젝트 다이어그램임
모델링 시에는 없었던, 그래서 코드에는 보이지 않던 관계가 오브젝트로 만들어진 후에 생성되는 것!
이제 이 다른 관심을 분리해서 클라이언트에게 떠넘겨보자!
현재는 UserDao() 클래스의 main() 메소드가 UserDao 클라이언트라고 볼 수 있음
좀 더 깔끔하게 구분하기 위해 UserDaoTest라는 이름의 클래스를 하나 만들고 UserDao에 있던 main() 메소드를 UserDaoTest로 옮겨보자
UserDao.java 코드 중 수정한 생성자
public UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
UserDaoTest.java
public class UserDaoTest {
public statis void main(String[] args) throws ClassNotFoundException, SQLException{
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao dao = new UserDao(connectionMaker);
...
}
}
UserDaoTest는 UserDao와 ConnectinMaker 구현 클래스와의 런타임 오브젝트 의존 관계를 설정하는 책임을 담당해야 함
이를 통해 모든 고객이 만족스럽게 DB 연결 기능을 확장해서 사용할 수 있게 됨
UserDao
- 자신의 관심사이자 책임인 사용자 데이터 엑세스 작업을 위해 SQL을 생성하고 이를 실행하는 데만 집중 가능
- DB 생성 방법이나 전략에 대해서는 고민할 필요가 없음
앞에서 사용했던 상속을 통한 확장 방법보다 더 깔끔하고 유연한 방법으로 UserDao와 ConnectionMaker 클래스들을 분리하고, 서로 영향을 주지 않으면서도 필요에 따라 자유롭게 확장할 수 있는 구조!
1.3.4. 원칙과 패턴
객체지향 설계와 프로그래밍의 이론을 통해 마지막으로 적용한 방법을 좀 더 체계적으로 살펴보자.
객체지향 설계 원칙(SOLID)
객체지향 설계 원칙 > 객체지향의 특징을 잘 살릴 수 있는 설계의 특징
좀 더 일반적인 상황에서 적용 가능한 설계 기준
객체지향 디자인 패턴은 대부분 객체지향 설계 원칙을 잘 지켜서 만들어져 있음
SOLID
- SRP(The Single Responsibility Principle) : 단일 책임 원칙
- OCP(The Open Closed Principle) : 개방 폐쇄 원칙
- LSP(The Lickov Substitution Principle) : 리스코프 치환 원칙
- ISP(The Interface Segregation Principle) : 인터페이스 분리 원칙
- DIP(The Dependency Inversion Peinciple) : 의존관계 역전 원칙
개방 폐쇄 원칙(OCP, Open-Closed Principle)
깔끔한 설계를 위해 적용 가능한 객체지향 설계 원칙 중 하나
클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 함
Ex)
UserDao는 DB 연결 방법이라는 기능을 확장하는 데는 열려 있음
UserDao에 전혀 영향을 주지 않고도 얼마든지 기능 확장 가능
동시에 UserDao 자신의 핵심 기능을 구현한 코드는 그런 변화에 영향을 받지 않고 유지할 수 있으므로 변경에는 닫혀 있음
높은 응집도와 낮은 결합도(high coherence and low coupling)
개방 폐쇄 원칙은 높은 응집도와 낮은 결합도라는 소프트웨어 개발의 고전적 원리로도 설명 가능
1. 응집도가 높다는 것
하나의 모듈, 클래스가 하나의 책임 또는 관심사에만 집중되어 있다는 것
2. 결합도가 낮다는 것
책임과 관심사가 다른 오브젝트 또는 모듈과는 낮은 결합도, 즉 느슨하게 연결된 형태를 유지하는 것이 바람직하다는 것, 즉 하나의 변경이 발생할 때 여타 모듈과 객체로 변경에 대한 요구가 전파되지 않는 상태
전략 패턴(Strategy Pattern)
전략 패턴은 디자인 패턴의 꽃이라고 불릴 만큼 다양하게 자주 사용되는 패턴
개방 폐쇄 원칙의 실현에도 가장 잘 들어맞는 패턴
전략 패턴은 자신의 기능 맥락(context)에서 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔서 사용할 수 있게 하는 디자인 패턴
여기서의 알고리즘
독립적인 책임으로 분리가 가능한 기능
'STUDY > Spring' 카테고리의 다른 글
[스프링] 오브젝트와 의존관계 6 (2) | 2022.10.31 |
---|---|
[스프링] 오브젝트와 의존관계 5 (0) | 2022.10.28 |
[스프링] 오브젝트와 의존관계 4 (0) | 2022.10.14 |
[스프링] 오브젝트와 의존관계 2 (0) | 2022.10.14 |
[스프링] 오브젝트와 의존관계 1 (0) | 2022.10.13 |