책임 분리
public class MovieFinder {
private MovieReader movieReader = new CsvMovieReader();
/**
* 저장된 영화 목록에서 감독으로 영화를 검색한다.
*
* @param directedBy 감독
* @return 검색된 영화 목록
*/
public List<Movie> directedBy(String directedBy) {
return movieReader.loadMovies()
.stream()
.filter(it -> it.getDirector().toLowerCase().contains(directedBy.toLowerCase()))
.collect(Collectors.toList());
}
/**
* 저장된 영화 목록에서 개봉년도로 영화를 검색한다.
*
* @param releasedYearBy
* @return 검색된 영화 목록
*/
public List<Movie> releasedYearBy(int releasedYearBy) {
return movieReader.loadMovies()
.stream()
.filter(it -> Objects.equals(it.getReleaseYear(), releasedYearBy))
.collect(Collectors.toList());
}
}
- 위의 코드에서 문제점있다. -> 만약 CsvMovieReader 가 아니라 XmlMovieReader 를 사용하고 싶다면? -> 필연적으로 MovieFinder 클래스를 변경해야 한다.
- 위의 문제는 책임을 분리하지 않았기 때문이다. -> 따라서, movieReader 를 결정짓는 책임을 내부가 아닌 외부로 빼야한다.
- 위의 코드는 아래와 같이 개선할 수 있다.
public class MovieFinder {
private final MovieReader movieReader;
public MovieFinder(MovieReader movieReader) {
this.movieReader = Objects.requireNonNull(movieReader);
}
/**
* 저장된 영화 목록에서 감독으로 영화를 검색한다.
*
* @param directedBy 감독
* @return 검색된 영화 목록
*/
public List<Movie> directedBy(String directedBy) {
return movieReader.loadMovies()
.stream()
.filter(it -> it.getDirector().toLowerCase().contains(directedBy.toLowerCase()))
.collect(Collectors.toList());
}
/**
* 저장된 영화 목록에서 개봉년도로 영화를 검색한다.
*
* @param releasedYearBy
* @return 검색된 영화 목록
*/
public List<Movie> releasedYearBy(int releasedYearBy) {
return movieReader.loadMovies()
.stream()
.filter(it -> Objects.equals(it.getReleaseYear(), releasedYearBy))
.collect(Collectors.toList());
}
}
- 위 코드를 보면, 생성자를 통해서 주입받는 식으로 변경했다. -> 이렇게 되면, movieReader 를 결정짓는 책임을 외부로 위임할 수 있다.
- 이렇게 되면 SOLID 원칙중 의존성 역전의 원칙을 만족할 수 있다. -> 상위 정책은 하위 정책에 의존하면 안된다. 하위 정책이 상위 정책에 정의된 추상 타입에 의존해야한다.
- 생성자를 통해서 외부에서 movieReader의 하위타입을 결정하고, MovieFinder 에서는 상위 정책인 MovieReader 와 같은 추상 타입에만 의존한다.
- 또한, final 키워드를 이용해서 객체가 생성되는 시점에서 값이 초기화 되도록 설정한다.
MovieFinder movieFinder = new MovieFinder(new CsvMovieReader());
List<Movie> result = movieFinder.directedBy("Michael");
assertEquals(3, result.size());
result = movieFinder.releasedYearBy(2015);
assertEquals(225, result.size());
- 위와 같이 생성자를 통해서 movieReader 를 결정한다.
적절한 인터페이스 소유권 지정
- 위의 그림처럼 CsvMovieReader, XmlMovieReader 는 MovieReader 에 의존하고 있다.
- 따라서 계방폐쇠의원칙과 의존성 역전의 원칙도 따르고 있다.
- 하지만 domain 패키지만 배포 후 재사용하겠다 한다면 어떻게 될까? -> domain 패키지의 MovieFinder 가 data 패키지의 MovieReader 에 의존하고 있기 때문이다.
- 그렇기 때문에 domain 패키지와 data 패키지는 항상 함께 배포가 되어야 한다.
- 또한 data 패키지의 변경에 domain 패키지도 영향을 받는다.
- 이러한 문제는 어떻게 해결할 수 있을까? -> MovieReader 를 도메인 패키지에 포함시킴으로써 해결할 수 있다.
- 이것을 분리된 인터페이스 패턴 (Seperated Interface Pattern) 이라고 부르며, 아래와 같이 MovieReader 가 domain 패키지로 포한된다.
- 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 합니다.
- 좋은 설계를 위해서는 적절하게 의존성을 역전시켜야한다. 그렇게 해야만 유연하고 재사용이 가능하다.
REFERENCES
- 박용권님의 스프링러너 스프링 아카데미
'객체지향' 카테고리의 다른 글
데코레이터 패턴 활용해서 관심사 분리하기 (0) | 2022.03.10 |
---|---|
상속 보다는 합성을 사용하라! (0) | 2022.03.10 |
디자인 패턴에 함수형 프로그래밍 적용하기 (0) | 2022.03.06 |
다형성 (0) | 2022.02.28 |
디자인 패턴 (0) | 2022.02.28 |