Link
Today
Total
12-18 11:41
Archives
관리 메뉴

초보개발자 긍.응.성

[디자인 패턴] 3. 데코레이터 패턴 (Decorator Pattern) 본문

설계패턴(Design Pattern)

[디자인 패턴] 3. 데코레이터 패턴 (Decorator Pattern)

긍.응.성 2024. 6. 16. 14:32
반응형

이 글은 헤드퍼스트 디자인패턴과 GoF 디자인패턴을 읽고 정리한 글입니다.

1. 데코레이터 패턴(Decorator Pattern)

데코레이터 패턴은 객체의 추가 요소를 동적으로 더할 수 있는 패턴입니다. 서브클래스를 만드는 것 보다 데코레이터 패턴을 통해 훨씬 유연하게 기능을 확장할 수 있습니다.

1.1. 구성 요소

Component

  • 동적으로 기능이 추가될 객체의 인터페이스입니다.
  • 꼭 인터페이스가 아니라 추상클래스여도 상관없지만 가능한 인터페이스로 만드는 것을 권장합니다.

ConcreteComponent

  • 기능이 추가될 수 있는 객체입니다. Component를 구현 또는 상속합니다.
  • 추후 설명드릴 Decorator들로 기능이 추가될 base 객체입니다.

Decorator

  • Component에 대한 참조를 가지며, Component를 구현 또는 상속합니다.
  • Component를 구현 또는 상속하기에 Component와 동일한 타입이며, Component 타입의 변수에 할당될 수 있습니다.

ConcreteDecorator

  • Decorator의 구현체입니다.
  • Decorator를 구현하기에, Component 타입의 변수에 할당될 수 있습니다.
  • Component를 꾸며줄 수 있으며, 다른말로 책임(Responsibility)를 추가할 수 있습니다.

1.2. 적용 방법

  1. 기반이 되는 부분들과 추가(확장)될 수 있는 기능들을 구분합니다.
  2. 기반이 되는 부분들은 ConcreteComponent, 추가될 수 있는 기능들은 ConcreteDecorator가 될 수 있습니다.
  3. ConcreteComponent로 부터 Component 인터페이스를 뽑아내 정의합니다.
  4. Decorator는 Component를 구현하고, Component 참조를 갖도록 합니다.

1.3. 적용 시기

  • 상속을 통한 기능확장이 비효율적일때 데코레이터 패턴을 고려할 수 있습니다. 확장 가능한 기능의 수가 많다면 그 조합으로 생성될 수 있는 하위 클래스의 개수는 엄청나게 늘어나게 되는데, 이는 데코레이터 패턴을 사용하여 해결할 수 있습니다.
  • 다른 객체에 영향을 끼치지 않으며 동적으로 책임이 추가 또는 제거되어야 할때 고려할 수 있습니다.

1.4. 정리

데코레이터 패턴은 OCP원칙을 잘 따르는 패턴 중 하나입니다. 새로운 기능을 추가할때 ConcreteDecorator 만 구현하면 Component나 다른 Decorator의 수정없이 적용가능합니다. 수정도 필요한 Decorator나 Compoent만 수정해주면 됩니다.

데코레이터 패턴은 상속에 비하여 기능을 추가하는데 더 유연하게 동작합니다. 동적으로 기능을 추가하는것은 상속의 경우 불가능하며, 추가된 기능을 수행할 클래스를 미리 정의해야합니다. 또한 기능들을 mix and match 하는것도 상속의 경우 엄청난 클래스의 생성이 필요하며, 이는 시스템 복잡도를 높이게됩니다.

반대로 데코레이터는 동적으로 기능을 추가 및 제거할 수 있으며, 다양한 조합이나 순서에 따라 추가적인 수정없이 적용가능합니다. 또한 파악하지 못한 기능 조합에 대한 요구 사항에도 바로 대응가능합니다. 상속이라면 새로운 클래스의 개발이 필요하겠지요.

하지만 데코레이터 패턴에서도 주의할 점이 있습니다. Decorator는 Component 타입이지만 완전히 데코레이팅 되기전과 된 후의 객체는 동일한 객체가 아닙니다. 그렇기에 equals를 통한 비교 등, 특정 object에 의존된 동작이 있다면 의도대로 동작하지 않을 수 있습니다.

Decorator가 많아지면 실제로 구현은 많이 들어있지 않은 작은 클래스들이 많이 생기게 됩니다. 데코레이터 패턴에 생소한 사람이 처음 코드를 본다면 파악이 어려울 수 있는데요, 이러한 상황을 대비해 항상 주석에 적용한 패턴에 대하여 명시하면 도움이 될 것 같습니다.

2. 데코레이터 패턴 구현 시 이슈 및 대응 방법

2.1. Interface conformance

데코레이터 클래스는 꾸며줄 Component의 인터페이스 타입이며 해당 인터페이스를 필드로 가져야 합니다.

2.2. Omitting the abstract Decorator class

추가할 기능이 오직 하나밖에 없는 상황이라면 추상 Decorator 클래스와 ConcreteDecorator를 두는것보다, 하나의 ConcreteDecorator를 두는 방법을 제안합니다.

2.3. Component 객체를 가볍게(lightweight) 유지하라

데코레이터 패턴에서 Component를 제외한 모든 구성요소(ConcreteCompoents, Decorator, ConcreteDecorators)들은 Component 클래스를 상속합니다. 그렇기에 Component 클래스를 최대한 가볍게 유지하여야 합니다. 최대한 필드도 없는 인터페이스로 선언하는것을 권장합니다.

2.4. 전략패턴과의 비교

데코레이터 패턴과 전략 패턴은 유사한 점이 있습니다. 그것은 동적으로 기능을 다르게 구성한다는 점입니다. 데코레이터 패턴의 경우는 기능을 추가로 구성할 수 있고, 전략 패턴은 사용할 전략을 바꿔서 적용할 수 있습니다. 책에서는 데코레이터 패턴의 경우 Skin을 갈아끼우는 것이고, 전략 패턴의 경우 Guts를 바꾸는 것으로 비유합니다. 이러한 비유의 원인은 Component에 적용되는 지점과도 관련이 있습니다.

데코레이터 패턴은 Decorator에서 Component를 겉에서 감싸는 방식으로 동작합니다. 이는 Decorator에서 Component의 참조를 갖고 있기 때문입니다.

반면 전략 패턴은 반대로 동작합니다. Component(Context)가 사용할 Strategy의 참조를 갖고 있기에, 내부에서 변경이 일어나는 것 처럼 동작합니다.

이 외에도 데코레이터 패턴과 전략패턴은 유사하지만 서로 다른 차이점이 존재합니다.

데코레이터 패턴의 Component는 lightweight하게 유지되어야 하는데요, 전략 패턴은 Component 객체가 heavyweight인 상황에 더 좋은 선택입니다. Strategy는 상속이 아닌 별개의 인터페이스를 상속하는 컴포넌트 이기에 Component의 크기가 커도 전략패턴을 구성하는데는 무리가 되지 않습니다.

3. 참고 링크

반응형
Comments