지난 포스트에서 다룬 빌더를 실습하며 빌더 패턴의 구조를 파악해봤다.
빌더 패턴 중에 추상 클래스가 추상 빌더를 가지고 하위 클래스에서 추상 클래스의 상속을 받으며 각 하위 클래스용 추상 빌더를 상속받아 만들 수 있다고 하는 부분을 직접 구현하고 장점을 알아보았다.
다음은 간단하게 구상한 클래스 목록이다. 모든 필드는 빌드로 추가할 수 있어야한다.
추상 클래스 - Movie Class
+ enum EQUIP { CAMERA, MIC, JIMMYJIP, PHONE ... }
# Set<EQUIP> equips
하위 클래스1 - ActionMovie Class
- boolean stuntMan
- boolean blood // required
하위 클래스2 - HorroMovie Class
+ enum CHARACTER { ZOMBIE, DRACULA, GHOST, VAMPIRE ... }
- int ageLimit
public class Movie {
public enum EQUIP {
CAMERA, MIC, JIMMYJIP, PHONE
}
protected final Set<EQUIP> equips;
abstract static class Builder<T extends Builder<T>> { // 재귀적인 타입 매개변수
EnumSet<EQUIP> equips = EnumSet.noneOf(EQUIP.class);
public T addEquip(EQUIP equip){
equips.add(Objects.requireNonNull(equip)); // 예외 처리
// self() 메소드를 사용함으로써 하위 클래스의 객체를 반환해 하위 클래스에 있는 메소드를 사용할 수 있도록 한다.
// 현재 Builder를 반환하면 하위 클래스에 있는 .ageLimit() 같은 메소드를 추가하지 못하기 때문
return self();
}
abstract Movie build(); // 추상 메소드로서 하위 클래스에서 재정의 하도록 한다.
protected abstract T self(); // JS에서 처럼 "self-type" 개념을 사용해서 메소드 체이닝이 가능하게 함
}
public Movie(Builder builder) {
equips = builder.equips.clone();
}
}
public class ActionMovie extends Movie{
private final boolean stuntMan;
private final boolean blood;
public static class Builder extends Movie.Builder<Builder> {
private boolean stuntMan = false;
private boolean blood = false;
public Builder(boolean blood) {
this.blood = blood;
}
public Builder addStuntMan() {
stuntMan = true;
return this;
}
@Override
public ActionMovie build() {
// ActionMovie 객체를 만들어 반환함으로 사용자가 타입 캐스팅을 하지 않아도 된다.
return new ActionMovie(this);
}
@Override
protected Builder self() {
return this;
}
}
public ActionMovie(Builder builder){
super(builder); // 상위 클래스 생성자 호출 -> equip 설정
stuntMan = builder.stuntMan;
blood = builder.blood;
}
}
public class HorrorMovie extends Movie {
public enum CHARACTER {
ZOMBIE, DRACULA, GHOST, WEREWOLF, VAMPIRE
}
private final Set<CHARACTER> characters;
private final int ageLimit;
public static class Builder extends Movie.Builder<Builder> {
private EnumSet<CHARACTER> chracters = EnumSet.noneOf(CHARACTER.class);
private int ageLimit;
public Builder addCharacter(CHARACTER character){
// 필수 필드는 생성자의 매개변수에 추가해 필수로 지정할 수 있도록 한다.
chracters.add(Objects.requireNonNull(character));
return self();
}
public Builder ageLimit(int age){
this.ageLimit = age;
return this;
}
@Override
HorrorMovie build() {
// HorrorMovie 객체를 만들어 반환함으로 사용자가 타입 캐스팅을 하지 않아도 된다.
return new HorrorMovie(this);
}
@Override
public Builder self(){
return this;
}
}
public HorrorMovie(Builder builder){
super(builder);
this.characters = builder.chracters.clone();
this.ageLimit = builder.ageLimit;
}
}
public class MovieClient {
ActionMovie actionMovie = new ActionMovie.Builder(true)
.addEquip(Movie.EQUIP.CAMERA)
.addEquip(Movie.EQUIP.MIC)
.addStuntMan()
.build();
HorrorMovie horrorMovie = new HorrorMovie.Builder()
.addEquip(Movie.EQUIP.CAMERA)
.addEquip(Movie.EQUIP.ZIPLINE)
.addEquip(Movie.EQUIP.MIC)
.addCharacter(HorrorMovie.CHARACTER.DRACULA)
.addCharacter(HorrorMovie.CHARACTER.VAMPIRE)
.ageLimit(19)
.build();
}
위 처럼 매개변수가 많더라도 사용하기 편하고 가독성도 좋은 코드를 작성할 수 있다. 또한 필드 추가,제거와 같이 확장성 있는 개발을 할 수 있다.
Effective Java 3rd Edition by Joshua Bloch
백기선님 유튜브 동영상 및 자료
https://www.youtube.com/watch?v=X7RXP6EI-5E&list=PLOFN6hDJLxo0MYZd1z6GCaRdFWX3kTb6A&index=2