Design Pattern, Decorator

2022. 2. 3. 17:00ETC/Design Patterns

반응형

 

Object Structural Pattern

Decorator Pattern

 

-----------------    INDEX     -----------------

 

Decorator Pattern ?

Structure

Sample Code: Java

관련 패턴

 

----------------------------------------------

 

 

Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending functionality.


- GoF Design Patterns

 

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

 

쉽게 말해 추가할 기능을 가진 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴입니다.

 

 

일상에서의 비유를 들어볼게요.

카페 메뉴를 생각해보면 아메리카노, 라떼, 카푸치노가 있고 라떼 중에서는

바닐라 시럽을 추가한 넣은 바닐라 라떼, 초코 시럽을 추가한 카페 모카가 있습니다.

 

위에서 말한 음료들은 모두 에소프레소라는 샷을 기본으로 제조됩니다.

아메리카노, 라떼, 바닐라 라떼, 카페 모카 등의 음료의 제조 과정을 코드로 구현할 때 이 데코레이터 패턴을 사용하면 아주 효과적으로 관리할 수 있습니다.

 

에소프레소에,      (... 베이스)

선택적으로 물이나 우유를 추가하고  ( ... 장식1)

또 선택적으로 바닐라 시럽이나 초코 시럽을 추가할 수 있습니다.   (... 장식2)

더 추가적으로 휘핑을 추가할 수도 있습니다   (... 장식3)

 

혹은, 에소프레소에 휘핑만을 추가할 수도 있고, 그 어떤 옵션을 추가하지 않고 에소프레소만으로도 음료가 됩니다.

 

장식자 패턴은 위와 같이 완전한 컴포넌트에 원하는 기능들을 추가적으로 장식하는 것입니다.

 

 

 

기능 추가 

기능을 추가하는 방법에는 상속(Inheritance)과 구성 두 가지가 있습니다.

 

먼저 상속은 기존의 객체를 확장해 새로운 기능을 추가해야 하는 경우가 있을때 이용해왔습니다.

하지만 상속은 상위 클래스와 하위 클래스 간에 강력한 결합 관계가 생성된다는 단점이 생깁니다.

또, 상속은 정적 방식으로 기능을 확장하기 때문에, 상황에 맞게 동적으로 확장해야 할 경우 상속으로 구현하는 것이 쉽지 않습니다.

 

원하는 기능만 상속해서 조합된 클래스를 생성해야하는데, 상속보다 유연한 방법으로 구성을 사용할 수 있습니다.

최근 모던 객체 지향 코드는 고전적인 상속보다는 구성을 이용해 객체를 확장하는데,

객체를 실행하는 도중에도 동적으로 다른 객체를 결합해 확장 가능하다는 장점이 있습니다.

 

 

// Composition
public class Engine {
    ... 
}

public class Car {
    Engine e = new Engine();
    ... 
}

구성 - Composition

 

 

 

// Aggregation
public class Address {  
    ...
}  
public class Person {  
     private Address address;
     
     public Person(Address address) {  
         this.address = address;
     }
     ...  
}

구성 - Aggregation

 

위의 코드와 같이 '한 부분'을 포함하는 관계로 구성할 수 있습니다.

구성은 주로 Composition을 의미하는 것 같은데 찾아보니 Aggregation도 있어서,

이 둘의 차이를 해당 링크를 통해 구분할 수 있었습니다.

Composition은 두 객체의 lifetime이 동일하며, aggregation에서는 차이가 있다고 하네요.

 

장식자 패턴은 동적으로 객체를 결합하기 위해 객체지향의 구성을 통해 확장합니다.

 

 

 

동적 할당

그렇다면, 위에서 말하는 동적 할당은 무엇일까요?

위임을 통해 객체를 확장하면 동적 객체 확장을 할 수 있는데,

동적 객체 확장이란 런타임으로 객체에 새로운 책임을 추가할 수 있는 방법입니다.

 

초기 클래스는 선언에 의해 객체의 내부 구조가 정의되는데, 한 번 생성된 객체의 구조를 변경하는 것은 어렵습니다.

하지만 객체를 복합 객체로 구성하면 위임을 통해 객체를 생성한 후에도 동적으로 구조를 변경할 수 있습니다.

객체가 확장될 때 사전에 책임이 확정되지 않았어도 적용할 수 있습니다.

 

장식자 패턴은 동적할당을 사용함으로써 추가하려는 미정의 객체 조합이 많을 때 유용한 패턴입니다.

 

 

 

투명성

데코레이터를 통해 확장된 객체는 동일한 인터페이스를 적용합니다.

클라이언트는 요청된 객체가 원본 객체인지 확장된 객체인지 모릅니다.

이러한 성질은 동일한 인터페이스를 사용한 객체에게 투명성을 부여합니다.

 

 

투명성으로 어떤 확장 객체이든 구별없이 사용할 수 있다.

 

 

장식자 패턴으로 확장된 객체는 원본 객체에 요청된 행위를 중간에서 가로채 더 확장된 행위로 대신 처리합니다.

투명성으로 원본 객체에 영향을 주지 않고 새로운 책임을 추가할 수 있습니다.

 

 

 

 

Structure

 

GoF Design Patterns

 

Component

- 책임을 동적으로 추가할 수 있는 객체의 인터페이스를 정의합니다.

객체의 투명성을 제공하는 인터페이스를 정의합니다.

 

 

ConcreteComponent

- 추가 책임(기능)이 부여될 수 있는 객체를 정의합니다.

Decorator로 장식(기능, 책임)을 추가할 base component를 정의합니다.

 

 

Decorator

- Component 객체를 reference 형태로 가지며,

- Component의 인터페이스를 따라 Decorator 인터페이스를 정의합니다.

 

 

ConcreteDecorator

- 객체들에게 책임(기능)을 추가합니다.

base component(ConcreteComponent)에 추가할 장식(기능, 책임)을 구현합니다.

 

 

 

 

Sample Code: Java

데코레이터 패턴은 굉장히 유용하게 사용할 수 있고, 많이 사용되는 패턴입니다.

실제 사용될 수 있을만한 코드로 예시를 가져왔는데요.

 

먼저, 메세지를 처리하는 기본 객체가 있습니다.

이 기본 객체에 암호화 기능을 추가하고 싶을 때, 기본 객체를 수정하지 않고 (OCP) 

기능을 추가하려 데코레이터 패턴을 사용합니다.

 

기본 객체 BaseMessage 에

평문의 순서를 뒤집는 추가 기능 Reverse 와,

암호화를 진행하는 Encrypt 를 사용하여 암호화를 시켜볼게요. 

 

 

 

 

Component  -  Message

interface Message {
    void sendMessage(String message);
}

 

 

ConcreteComponent  -  BaseMessage

public class BaseMessage implements Message {
    @Override
    public void sendMessage(String message) {
        System.out.println("Message to send : " + message);
    }
}

 

 

Decorator - MessageDecorator

public abstract class MessageDecorator implements Message {
    protected Message base;

    public MessageDecorator(Message message) {
        this.base = message;
    }

    public abstract void sendMessage(String message);

    public Message getDecoratedMessage() {
        return this.base;
    }
}

 

데코레이터를 보면 위의 Structure에서 소개한 reference로 Message base 를 확인할 수 있습니다.

이 base component를 사용하는 데코레이터를 확장하여 장식자를 추가할 수 있게 됩니다.

 

 

ConcreteDecorator (1) - Reverse

public class Reverse extends MessageDecorator {
    public Reverse(Message message) {
        super(message);
    }

    public String reverser(String messageToReverse) {
        return new StringBuilder(messageToReverse).reverse().toString();
    }

    @Override
    public void sendMessage(String message) {
        System.out.println("reverse : " + message);
        this.getDecoratedMessage().sendMessage(reverser(message));
    }
}

this.getDecoratedMessage()를 사용해 base component에 reverse된 메세지를 전달하는 것을 확인할 수 있습니다.

 

 

ConcreteDecorator (2) - Encrypt

public class Encrypt extends MessageDecorator {
    public Encrypt(Message message) {
        super(message);
    }

    public String B64Encode(String messageToEncrypt) {
        byte[] bytesEncoded = Base64.getEncoder().encode(messageToEncrypt.getBytes());
        return new String(bytesEncoded);
    }

    @Override
    public void sendMessage(String message) {
        System.out.println("encript : " + message);
        this.getDecoratedMessage().sendMessage(B64Encode(message));
    }
}

 

마찬가지로, this.getDecoratedMessage() 를 사용해 base component에 암호화된 메세지를 전달하는 것을 확인할 수 있습니다.

 

 

Client

public class Client {
    public static void main(String[] args) {
        String message = "seCrEtMesSagE";
        System.out.println("Message to send : " + message);

        Message after = new Reverse(new Encript(new BaseMessage()));
        after.sendMessage(message);
    }
}

 

BaseMessage를 감싼 Encrypt 를 감싼 Reverse 객체에 message를 전달하면

어떤 순서로 실행되는지 바로 예상이 간다면 잘 이해하고 있다는 증거입니다 👏🏻

 

아래는 실제 실행을 파악할 수 있는 출력물입니다.

 

 

출력

Message to send : seCrEtMesSagE
reverse : seCrEtMesSagE
encript : EgaSseMtErCes
Message to send : RWdhU3NlTXRFckNlcw==

 

 

 

 

관련 패턴

C- Creational Patterns  |  S - Structural Patterns  |  B - Behavioral Patterns

 

 

S: Composite 

장식자 패턴을 복합체 패턴과 연관시켜 학습하는 것이 좋습니다.

두 패턴 모두 한 개 이상의 구성을 가진 복합 객체입니다.

 

차이는 패턴을 사용하는 목적입니다.

Composite 패턴이 객체를 합성하는 것이라면,

Decorator 패턴은 새로운 객체의 행동을 추가하는 것입니다.

 

또, Composite 패턴의 경우 트리 구조로 인해 좌우 폭, 상하 관계 등 다양한 형태의 크기로 확장될 수 있습니다.

이와 달리 Decorator 패턴은 상하 계층으로만 확장된다는 특징이 있습니다.

 

 

S: Adapter

언뜻보면 기본 객체에 변화를 추가해 새로운 객체를 생성하는 것이 헷갈릴 수 있는데요.

어댑터 패턴은 인터페이스를 변경하지만

장식자 패턴은 기능, 행동을 변경합니다.

 

 

B: Strategy

장식자 패턴이 커지거나 무거워질 때는 전략패턴을 같이 응용하는 것도 좋습니다.

 

장식자 패턴은 겉모양을 변경하는 반면, 전략 패턴은 내부의 변화를 가집니다.

또한 전략 패턴은 자신만의 인터페이스에 따라 처리되는 반면,

장식자 패턴은 컴포넌트에서 정의된 인터페이스를 따라 동작합니다.

 

 

 

 

그럼 지금까지 Decorator Pattern에 대해 알아보았습니다.

오타나 잘못된 내용이 있다면 댓글로 남겨주세요!

감사합니다 ☺️ 

 

 

 

모든 Design Patterns 모아보기

 

Design Patterns

안녕하세요. GoF 디자인 패턴을 정리하고자 합니다. 디자인 패턴은 일주일 전부터 공부를 시작했는데, 스스로 설명하듯 적는게 익히는데 도움이 클 것같아 정말 오랜만에 시리즈로 포스팅하려

gngsn.tistory.com

 

 

반응형

'ETC > Design Patterns' 카테고리의 다른 글

Design Pattern, Iterator  (0) 2022.02.10
Design Pattern, Command  (0) 2022.02.06
Design Pattern, Composite  (0) 2022.02.01
Design Pattern, Singleton  (0) 2022.01.26
Design Pattern, Mediator  (0) 2022.01.24