Decorator Pattern
포드래 베스퍼(Podrea Vesper)는 시합에서 우리가 속임수를 사용한 것을 적발했다. 우리에게는 하나의 선택권이 있다: 경찰에 체포되거나 그의 슈퍼 기사를 시합에 넣어주는 것이다.
페드로: 난 감옥에 가고 싶지 않아요.
이브: 저도요.
페드로: 그를 위해 한번 더 해보죠.
이브: 같은 방식이죠?
페드로: 비슷하지만, 똑같지는 않아요. 코만도는 군인이어서 시합에 참여하는 것이 허용되지 않죠. 그래서 우리가 맞추어 넣은 거구요. 하지만 기사는 시합 참여가 가능하니, 맞출 필요가 없구요. 기존 것에 기능을 추가하면 되요.
이브: 상속이나 합성?
페드로: 합성, 런타임에 행위를 바꾸는 데코레이터 패턴의 주요 목적이죠.
이브: 그러면 이 슈퍼 기사를 어떻게 할까요?
페드로: 그들은 Galahad 기사를 사용해서 더 많은 HP와 강력 갑옷을 장식할 계획이에요.
이브: 헤, 경찰이 게임을 하다니 재밌네요.
페드로: 네, 기사를 추상 클래스로 만듭시다.
public class Knight { protected int hp; private Knight decorated; public Knight() { } public Knight(Knight decorated) { this.decorated = decorated; } public void attackWithSword() { if (decorated != null) decorated.attackWithSword(); } public void attackWithBow() { if (decorated != null) decorated.attackWithBow(); } public void blockWithShield() { if (decorated != null) decorated.blockWithShield(); } }
이브: 그러면 저기서 무엇을 개선해야 되죠?
페드로: 인터페이스 대신에 클래스 Knight를 만들어서 hp(hit point) 멤버변수에 접근해요. 그러고 나서 2개의 생성자를 만드는데, 하나는 표준 행위를 위한 디폴트이고, 다른 하나는 데코레이트된 객체에 호출을 위임하는 데코레이트된 생성자이죠.
이브: 인터페이스 대신 추상 클래스를 쓰는 게 맞아요?
페드로: 아니요, 하지만 비슷한 행위를 하는 2개의 클래스를 피하고, 각 데코레이트된 객체를 기본 구현으로 채우는 거죠. 각 메소드의 구현을 강제하는 대신.
이브: 오케이, 특수 갑옷은요?
페드로: 역시 쉬워요.
public class KnightWithPowerArmor extends Knight { public KnightWithPowerArmor(Knight decorated) { super(decorated); } @Override public void blockWithShield() { super.blockWithShield(); Armor armor = new PowerArmor(); armor.block(); } } public class KnightWithAdditionalHP extends Knight { public KnightWithAdditionalHP(Knight decorated) { super(decorated); this.hp += 50; } }
페드로: 경찰의 요구 사항을 충족하는 2개의 데코레이터에요. 이제 슈퍼 기사를 만들 수 있어요. 이 슈퍼 기사는 Galahad와 같은 동작을 하지만, 특수 갑옷을 입고 있고, hp 점수도 50점 더 높아요.
Knight superKnight = new KnightWithAdditionalHP( new KnightWithPowerArmor( new Galahad()));
이브: 괜찮은 트릭이네요.
페드로: 자 이제 클로저로 비슷한 행위를 보여줘 보시죠.
이브: 여기요.
(def galahad {:name "Galahad" :speed 1.0 :hp 100 :attack-bow-fn attack-with-bow :attack-sword-fn attack-with-sword :block-fn block-with-shield}) (defn make-knight-with-more-hp [knight] (update-in knight [:hp] + 50)) (defn make-knight-with-power-armor [knight] (update-in knight [:block-fn] (fn [block-fn] (fn [] (block-fn) (block-with-power-armor))))) ;; create the knight (def superknight (-> galahad make-knight-with-power-armor make-knight-with-more-hp)
페드로: 똑같은 기능이네요.
이브: 네, 다만 특수 갑옷 데코레이터(make-knight-with-power-armor)만 주의해서 보세요.