모르지 않다는 것은 아는것과 다르다.

객체지향

역할과 책임분리

채마스 2022. 3. 10. 23:00

책임 분리

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

  • 박용권님의 스프링러너 스프링 아카데미