상속
- 위의 사진처럼 MovieFinder 라는 부모 클래스가 몇몇 기능의 구현을 자식클래스에게 위임한다.
- 상속을 사용하면 부모 클래스의 멤버를 재사용할 수 있는 장점이 있다.
- 또한 확장에 용이하다. 만약 요구사항으로 인해 구현체가 늘어났다고 해서 MovieFinder 가 변하지 않기 때문이다.
- 하지만, 상속은 캡슐화를 위배하고, 설계를 유연하지 못하게 만든다는 단점이 있다.
합성
- 합성은 다른 객체의 인스턴스를 자신의 인스턴스 변수로 포함해서 재사용하는 방법을 의미한다.
- SOLID 원칙중 계방 폐쇄의 원칙(OCP) 을 만족할 수 있다. -> 소프트웨어는 객체는 확장에는 열려있고, 변경에는 닫혀 있어야 한다.
- MovieReader 를 통해서 자유롭게 확장할 수 있다. 또한 변경시 MovieFinder 는 변경할 필요가 없기 때문이다.
- Effective Java 에서도 상속보다는 합성을 사용하고, 추상 클래스보다는 인터페이스를 사용하라고 나와있다.
- 또한 객체는 인터페이스를 통해서 참조하라고 언급 되어있다.
코드예시
상속 코드
public abstract 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());
}
public abstract List<Movie> loadMovies();
}
- MovieFinder 를 추상 클래스로 설정하고, loadMovies 메소드를 추상메소드로 등록한다.
public class CsvMoviesFinder extends MovieFinder {
/**
* 영화 메타데이터를 읽어 저장된 영화 목록을 불러온다.
*
* @return 불러온 영화 목록
*/
@Override
public List<Movie> loadMovies() {
try {
final InputStream content = getMetadataResource().getInputStream();
final Function<String, Movie> mapCsv = csv -> {
try {
// split with comma
String[] values = csv.split(",");
String title = values[0];
List<String> genres = Arrays.asList(values[1].split("\\|"));
String language = values[2].trim();
String country = values[3].trim();
int releaseYear = Integer.valueOf(values[4].trim());
String director = values[5].trim();
List<String> actors = Arrays.asList(values[6].split("\\|"));
URL imdbLink = new URL(values[7].trim());
String watchedDate = values[8];
return Movie.of(title, genres, language, country, releaseYear, director, actors, imdbLink, watchedDate);
} catch(IOException error) {
throw new ApplicationException("mapping csv to object failed.", error);
}
};
return new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))
.lines()
.skip(1)
.map(mapCsv)
.collect(Collectors.toList());
} catch(IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
}
}
- CsvMoviesFinder 는 MovieFinder 를 상속받고 loadMovies 를 구현한다.
public class XmlMoviesReader extends MovieFinder {
@Override
public List<Movie> loadMovies() {
try {
final InputStream content = getMetadataResource().getInputStream();
final Source source = new StreamSource(content);
final MovieMetadata metadata = (MovieMetadata) unmarshaller.unmarshal(source);
return metadata.toMovies();
} catch (IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
}
}
- XmlMoviesFinder 또한 MovieFinder 를 상속받고 loadMovies 를 구현한다.
MovieFinder movieFinder = new CsvMovieFinder();
List<Movie> result = movieFinder.directedBy("Michael");
assertEquals(3, result.size());
result = movieFinder.releasedYearBy(2015);
assertEquals(225, result.size());
- 위와 같이 상속을 적용할 수 있다.
- 이렇게 되면 부모 클래스에 기본적인 알고리즘의 흐름을 구현하고 중간에 필요한 처리를 자식 클래스에서 위임할 수 있다.
- 이러한 구조를 디자인 패턴에서는 템플릿 메소드 패턴이라고 한다.
- 하지만 상속보단 합성을 적용하는 것이 바람직하다. 위의 코드를 합성으로 바꿔보자.
합성 예시
public interface MovieReader {
List<Movie> loadMovies();
}
public class CsvMovieReader implements MovieReader {
/**
* 영화 메타데이터를 읽어 저장된 영화 목록을 불러온다.
*
* @return 불러온 영화 목록
*/
@Override
public List<Movie> loadMovies() {
try {
final InputStream content = getMetadataResource().getInputStream();
final Function<String, Movie> mapCsv = csv -> {
try {
// split with comma
String[] values = csv.split(",");
String title = values[0];
List<String> genres = Arrays.asList(values[1].split("\\|"));
String language = values[2].trim();
String country = values[3].trim();
int releaseYear = Integer.valueOf(values[4].trim());
String director = values[5].trim();
List<String> actors = Arrays.asList(values[6].split("\\|"));
URL imdbLink = new URL(values[7].trim());
String watchedDate = values[8];
return Movie.of(title, genres, language, country, releaseYear, director, actors, imdbLink, watchedDate);
} catch(IOException error) {
throw new ApplicationException("mapping csv to object failed.", error);
}
};
return new BufferedReader(new InputStreamReader(content, StandardCharsets.UTF_8))
.lines()
.skip(1)
.map(mapCsv)
.collect(Collectors.toList());
} catch(IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
}
}
public class XmlMovieReader implements MovieReader {
@Override
public List<Movie> loadMovies() {
try {
final InputStream content = getMetadataResource().getInputStream();
final Source source = new StreamSource(content);
final MovieMetadata metadata = (MovieMetadata) unmarshaller.unmarshal(source);
return metadata.toMovies();
} catch (IOException error) {
throw new ApplicationException("failed to load movies data.", error);
}
}
}
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());
}
}
- 앞서 언급했듯이, 객체를 MovieReader 라는 인터페이스를 통해서 참조 하고있다.
- 이렇게 인터페이스를 통해서 합성하면 구현을 효과적으로 캡슐화 할 수 있다.
- 또한 참조되는 인스턴스를 교체하기 쉽기 때문에 설계를 유연하게 만들어 준다.
- 이러한 구조를 디자인 패턴에서는 전략 패턴이라고 한다.
- 따라서 상속을 합성으로 대체할 수 있다면 대체하는 것이 바람직하다.
REFERENCES
- 박용권님의 스프링러너 스프링 아카데미
'객체지향' 카테고리의 다른 글
리플렉션을 활용한 동적 프락시 구현 (0) | 2022.03.10 |
---|---|
데코레이터 패턴 활용해서 관심사 분리하기 (0) | 2022.03.10 |
역할과 책임분리 (0) | 2022.03.10 |
디자인 패턴에 함수형 프로그래밍 적용하기 (0) | 2022.03.06 |
다형성 (0) | 2022.02.28 |