관리 메뉴

나만을 위한 블로그

데코레이터(Decorator) 패턴이란? 본문

개인 공부/디자인 패턴

데코레이터(Decorator) 패턴이란?

참깨빵위에참깨빵 2022. 1. 16. 00:27
728x90
반응형

데코레이터의 원형인 Decorate는 아래와 같은 뜻을 갖고 있다.

 

장식하다, 꾸미다

 

즉 '어떤 장면, 부분 따위를 인상 깊고 의미하게 만들다'라는 의미를 가진 단어다. 원래의 것에 어떤 걸 추가하는 뉘앙스의 단어이니 데코레이터 패턴도 이 의미를 바탕으로 이름이 붙여진 것 아닐까?

위키백과에선 아래와 같이 설명한다.

 

https://ko.wikipedia.org/wiki/%EB%8D%B0%EC%BD%94%EB%A0%88%EC%9D%B4%ED%84%B0_%ED%8C%A8%ED%84%B4

 

데코레이터 패턴 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

데코레이터 패턴이란 주어진 상황, 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이 될 수 있다

 

https://en.wikipedia.org/wiki/Decorator_pattern

 

Decorator pattern - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Design pattern in object-oriented programming In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynami

en.wikipedia.org

객체 지향 프로그래밍에서 데코레이터 패턴은 같은 클래스의 다른 객체 동작에 영향을 주지 않고 개별 객체에 동작을 동적으로 추가할 수 있는 디자인 패턴이다. 데코레이터 패턴은 고유한 관심 영역이 있는 클래스 간에 기능을 나눌 수 있으므로 단일 책임 원칙을 준수하는 데 유용하다. 완전히 새로운 객체를 정의하지 않고도 객체의 동작을 보강할 수 있기 때문에 데코레이터를 쓰는 게 서브클래싱보다 효율적일 수 있다

 

이 데코레이터 패턴은 아래의 경우에 사용된다.

 

  • 런타임 시 객체에 동적으로 책임을 추가할 때
  • 기능 확장을 위해 서브클래싱보다 유연한 대안을 사용해야 할 때

 

코드로 확인하기 전에 다른 곳에선 데코레이터 패턴을 어떻게 설명하는지 확인해보자.

 

https://refactoring.guru/design-patterns/decorator

 

Decorator

/ Design Patterns / Structural Patterns Decorator Also known as: Wrapper Intent Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. Prob

refactoring.guru

데코레이터 패턴은 동작을 포함하는 특수 래퍼(wrapper) 객체 안에 객체를 배치해서 객체에 새 동작을 추가할 수 있는 구조적 디자인 패턴이다. 사용자에게 중요한 이벤트를 알릴 수 있는 알림 라이브러리를 만들고 있다고 가정한다. 라이브러리 초기 버전은 몇 개의 필드, 생성자, 단일 송신 메서드를 가진 Notifier 클래스에 기반을 둔다. 메서드는 클라이언트의 메시지 인수를 수락하고 생성자를 통해 통보자에게 전달된 전자 메일 목록으로 메시지를 보낼 수 있다. 클라이언트 역할을 하는 서드파티 앱은 알림 객체를 한 번 만들어 구성한 뒤 중요한 일이 발생할 때마다 쓰도록 돼 있다.

어느 시점에서 당신은 라이브러리 사용자가 이메일 알림 이상을 기대한다는 걸 알게 된다. 많은 사람들이 중요한 문제는 SMS로 받기를 원하고, 누구는 페이스북 알림, 기업 사용자는 슬랙 알림도 원한다.

뭐 얼마나 어렵겠는가? Notifier 클래스를 확장하고 추가 알림 메서드를 새 하위 클래스에 넣었다. 이제 클라이언트는 원하는 알림 클래스를 인스턴스화하고 모든 추가 알림에 사용하도록 되어 있다. 그런데 누군가가 "왜 여러 알림을 동시에 쓸 수 없나요? 만약 집에 불이 났다면 모든 채널을 통해 불이 났다는 정보를 받고 싶을 텐데요"
한 클래스 안에 여러 알림 방법을 결합한 특수 하위 클래스를 만들어 이 문제를 해결하려고 했던 이 방식은 라이브러리 코드 뿐 아니라 클라이언트 측 코드도 엄청나게 부풀릴 것이 분명해졌다.

객체의 동작을 변경해야 할 때 가장 먼저 떠오르는 건 클래스 확장이다. 그러나 상속에는 몇 가지 심각한 경고가 있다.

1. 상속은 정적(static)이다. 런타임 시 기존 객체의 동작을 바꿀 수 없다. 전채 객체를 다른 하위 클래스에서 생성된 다른 객체로만 바꿀 수 있다
2. 하위 클래스는 하나의 상위 클래스만 가질 수 있다. 대부분 언어에서 상속은 클래스가 동시에 여러 클래스의 동작을 상속하도록 허용하지 않는다

이 주의 사항을 극복하는 방법 중 하나는 상속 대신 집계 또는 구성을 사용하는 것이다. 두 대안 모두 거의 동일한 방식으로 작동한다. 한 객체를 다른 객체에 대한 참조를 갖고 일부 작업을 위임하는 반면 상속을 사용하면 객체 자체가 슈퍼 클래스에서 동작을 상속해 해당 작업을 수행할 수 있다. 이 접근 방식을 쓰면 연결된 "도우미(helper)" 객체를 다른 객체로 쉽게 대체해서 런타임에 컨테이너의 동작을 변경할 수 있다. Wrapper는 패턴의 주요 아이디어를 명확하게 표현하는 데코레이터 패턴의 별명이다. 래퍼는 일부 대상 객체와 연결할 수 있는 객체다. 래퍼에는 대상과 동일한 메서드 집합이 포함돼 있으며 대상이 수신하는 모든 요청을 대상으로 위임한다. 그러나 래퍼들은 대상에 요청을 전달하기 전이나 후에 뭔가를 함으로써 결과를 바꿀 수 있다...(중략)

 

https://www.tutorialspoint.com/design_pattern/decorator_pattern.htm

 

Design Patterns - Decorator Pattern

Design Patterns - Decorator Pattern Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of design pattern comes under structural pattern as this pattern acts as a wrapper to existing clas

www.tutorialspoint.com

데코레이터 패턴을 쓰면 사용자가 구조를 변경하지 않고 기존 객체에 새 기능을 추가할 수 있다. 이런 유형의 디자인 패턴은 이 패턴이 기존 클래스에 대한 래퍼 역할을 하기 때문에 구조적 패턴에 속한다. 이 패턴은 원래 클래스를 래핑하고 클래스 메서드 시그니처를 그대로 유지하는 추가 기능을 제공하는 데코레이터 클래스를 만든다

 

https://www.javatpoint.com/decorator-pattern

 

Decorator Pattern - Javatpoint

Decorator Pattern with Patterns, design, Creational Design, Abstract Factory Pattern, singleton design patterns, Adapter, Visitor Pattern, Template Pattern, Command Pattern, State Pattern, java etc.

www.javatpoint.com

데코레이터 패턴은 객체에 유연한 추가 책임을 동적으로 첨부한다고 말한다. 즉 데코레이터 패턴은 런타임에 객체의 기능을 확장하기 위해 상속 대신 구성(composition)을 사용한다. 데코레이터 패턴은 wrapper라고도 한다.

 

핵심은 런타임에 객체의 기능을 확장하는 것이다. 이걸 생각하면서 아래에 가져온 코드를 보자. 코드 순서는 이렇다.

 

  1. Shape 인터페이스와 이 인터페이스를 구현한 구체(Concrete) 클래스(Circle, Rectangle) 생성
  2. Shape 인터페이스를 구현하고 인스턴스 변수로 Shape 객체를 갖는 추상 데코레이터 클래스(ShapeDecorator) 생성
  3. 추상 데코레이터 클래스를 구현한 구체 클래스(RedShapeDecorator) 생성
  4. 메인 메서드에서 사용

 

먼저 Shape 인터페이스를 만든다.

 

public interface Shape {
    void draw();
}

 

그리고 이 인터페이스를 구현한 클래스를 2개 만든다.

 

public class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shape : Rectangle");
    }
}
public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Shape : Circle");
    }
}

 

다음으로 추상 데코레이터 클래스를 만든다. 이 클래스는 Shape 인터페이스의 구현 클래스기도 하다.

 

public abstract class ShapeDecorator implements Shape {

    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape) {
        this.decoratedShape = decoratedShape;
    }

    @Override
    public void draw() {
        decoratedShape.draw();
    }
}

 

그리고 이 ShapeDecorator를 확장하는 구체 클래스를 만든다. 테두리가 붉게 설정됐다고 설정하는 메서드가 있다.

 

public class RedShapeDecorator extends ShapeDecorator {

    public RedShapeDecorator(Shape decoratedShape) {
        super(decoratedShape);
    }

    @Override
    public void draw() {
        super.draw();
        setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape) {
        System.out.println("Border Color : Red");
    }

}

 

이제 메인 메서드에서 사용해본다.

 

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape redCircle = new RedShapeDecorator(new Circle());
        Shape redRectangle = new RedShapeDecorator(new Rectangle());

        System.out.println("Circle with normal border");
        circle.draw();

        System.out.println("\nCircle of red border");
        redCircle.draw();

        System.out.println("\nRectangle of red border");
        redRectangle.draw();
    }
}

 

결과는 아래와 같이 나온다.

 

 

그럼 이 데코레이터 패턴의 장단점은 뭐가 있을까?

 

https://www.ionos.com/digitalguide/websites/web-development/what-is-the-decorator-pattern/

 

Decorator pattern: a pattern for dynamic class expansion

If a class is to be expanded to include new functionalities, the decorator design pattern offers the suitable implementation strategies. But what is behind the pattern?

www.ionos.com

장점 단점
높은 유연성 높은 복잡성 (특히 데코레이터 인터페이스)
상속 없이 클래스의 기능 확장 가능 초보자 친화적이지 않음
읽을 수 있는 코드 많은 수의 객체
리소스 최적화 어려운 디버깅 프로세스

 

https://neillmorgan.wordpress.com/2010/02/07/decorator-pattern-pros-and-cons/

 

Decorator Pattern Pros and Cons

Pros:  Decorators provide a flexible alternative to subclassing for extending functionality Decorators allow behavior modification at runtime rather than going back into existing code and making ch…

neillmorgan.wordpress.com

장점 단점
기능 확장을 위해 서브클래싱에 대한 유연한 대안 제공 디자인에 많은 객체를 생성할 수 있고 남용 시
복잡할 수 있다
런타임에 동작 수정 허용 클라이언트가 구성 요소의 구체적인 유형에 크게
의존하는 경우 데코레이터가 문제를 일으킬 수 있음
데코레이터는 구성 요소를 원하는 수의 데코레이터로
래핑할 수 있기 때문에 순열 문제에 대한 좋은 솔루션임
데코레이터는 구성요소를 인스턴스화해야 할 뿐 아니라
여러 데코레이터로 래핑해야 하기 때문에 구성요소를
인스턴스화하는 프로세스를 복잡하게 만들 수 있음
개방 폐쇄 원칙 지원 데코레이터가 다른 데코레이터를 추적하도록 하는 것은
복잡할 수 있다. 데코레이터 체인의 여러 레이어를
뒤돌아 보면 데코레이터 패턴이 실제 의도 이상으로
멀어지기 시작하기 때문이다

 

위의 Shape 예제에서 보았듯이 저 기능 하나를 구현하는 데만 꽤 많은 클래스, 인터페이스가 만들어졌다. 이렇게 되면 유닛 테스트가 어려워질 수 있으니 패턴을 사용하면 좋을 때가 언제인지 확인하고, 설계 시 다른 방법으로 더 쉽게 만들 수 있는 방법은 없는지 고민해보는 게 좋을 것 같다.

반응형
Comments