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

koline·2023년 9월 4일
0

디자인패턴

목록 보기
10/24

데코레이터 패턴


기존에 구현되어 있는 클래스에 필요한 기능을 추가해 나가는 설계 패턴으로 기능 확장이 필요할 때 객체 간의 결합을 통해 기능을 동적으로 유연하게 확장할 수 있게 해주어 상속의 대안으로 사용하는 디자인 패턴이다.

Decorator(장식자)라는 이름에서 알 수 있듯이 마치 기본 제품에 포장지나 외부 디자인을 살짝 변경해 줌으로써 새로운 기능을 부여하는 것과 같이, 객체 지향 프로그래밍에서 원본 객체에 대해서 무언가를 장식하여 더 멋진 기능을 가지게 만들수 있게 해준다.

데코레이터 패턴을 이용하면 필요한 추가 기능의 조합을 런타임에서 동적으로 생성할 수 있다. 데코레이터할 대상 객체를 새로운 행동들을 포함한 특수 장식자 객체에 넣어서 행동들을 해당 장식자 객체마다 연결시켜, 서브클래스로 구성할때 보다 훨씬 유연하게 기능을 확장 할 수 있다. 그리고 기능을 구현하는 클래스들을 분리함으로써 수정이 용이해진다.


구조


  1. Component(Interface) : 원본 객체와 장식된 객체 모두를 묶는 역할
  2. ConcreteComponent : 원본 객체 (데코레이팅 할 객체)
  3. Decorator : 추상화된 장식자 클래스
    원본 객체를 합성(composition)한 wrappee 필드와 인터페이스의 구현 메소드를 가지고 있다
  4. ConcreteDecorator : 구체적인 장식자 클래스
    부모 클래스가 감싸고 있는 하나의 Component를 호출하면서 호출 전/후로 부가적인 로직을 추가할 수 있다.

여기서 Component는 기본 기능을 담당하는 ConcreteComponent와 추가 기능을 담당하는 Decorator의 공통 기능을 정의한다.

ConcreateDecorator은 Decorator의 하위 클래스로, Component의 기본 기능과 Decorator의 추가 기능을 모두 제공한다. 그래서 ConcreateDecorator는 ConcreteComponent에 대한 참조가 필요한데, 조합을 통해서 표현한다.


구현


// Weapon.java (Component) => interface로 구현
public interface Weapon {
	// operation()
    void fire();
}

// BasicWeapon.java (ConcreteComponent)
public class BasicWeapon implements Weapon {
	// 부모객체의 operation 상속받아 구현
    @Override
    public void fire() {
        System.out.println("[ConcreteWeapon] Opening fire...");
    }
}

// WeaponAccessory.java (Decorator)
public class WeaponAccessory implements Weapon {
    // wrappee 필드 (원본 객체)
    private Weapon weapon;

    public WeaponAccessory(Weapon weapon) {
        this.weapon = weapon;
    }

    @Override
    public void fire() {
        weapon.fire();
    }
}

// Scope.java (ConcreteDecorator)
public class Scope extends WeaponAccessory {
    public Scope(Weapon weapon) {
        super(weapon);
    }

    @Override
    public void fire() {
        aim();
        super.fire();
    }

    public void aim() {
        System.out.println("[Scope] Target aimed...");
    }
}


// ButtStock.java (ConcreeteDecorator)
public class ButtStock extends WeaponAccessory {
    public ButtStock(Weapon weapon) {
        super(weapon);
    }
    
    @Override
    public void fire() {
        shoulder();
        super.fire();
    }
    public void shoulder() {
        System.out.println("[ButtStock] Weapon shouldered...");
    }
}

// Silencer.java (ConcreteDecorator)
public class Silencer extends WeaponAccessory {
    public Silencer(Weapon weapon) {
        super(weapon);
    }

    @Override
    public void fire() {
        silence();
        super.fire();
    }

    public void silence() {
        System.out.println("[Silencer] Weapon silenced...");
    }
}

데코레이터 순서는 원본 대상 객체 생성자를 장식자 생성자가 래핑(wrapping) 하는 형태로 생성하여 (ex) new 장식자( new 원본() )) 원본에 장식자를 추가하여 장식(Decorate)하는 형태라고 할 수 있다. 이러한 방식을 사용해 런타임에서 유연하게 객체의 기능들을 수정하고 조합하는데 유용하게 사용될 수 있다.

// Client.java
public class Client {
    public static void main(String[] args) {
        Weapon weapon = new ButtStock(new BasicWeapon());
        System.out.println("----- firing weapon 1 -----");
        weapon.fire();

        weapon = new Silencer(new Scope(new BasicWeapon()));
        System.out.println("----- firing weapon 2 -----");
        weapon.fire();
    }
}

// 실행결과
----- firing weapon 1 -----
[ButtStock] Weapon shouldered...
[ConcreteWeapon] Opening fire...
----- firing weapon 2 -----
[Silencer] Weapon silenced...
[Scope] Target aimed...
[ConcreteWeapon] Opening fire...



목적

  1. 객체 책임과 행동이 동적으로 상황에 따라 다양한 기능이 빈번하게 추가/삭제되는 경우
  2. 객체의 결합을 통해 기능이 생성될 수 있는 경우
  3. 객체를 사용하는 코드를 손상시키지 않고 런타임에 객체에 추가 동작을 할당할 수 있어야 하는 경우
  4. 상속을 통해 서브클래싱으로 객체의 동작을 확장하는 것이 어색하거나 불가능 할 때

장점

  1. 데코레이터를 사용하면 서브클래스를 만들때보다 훨씬 더 유연하게 기능을 확장할 수 있다.
  2. 객체를 여러 데코레이터로 래핑하여 여러 동작을 결합할 수 있다.
  3. 컴파일 타임이 아닌 런타임에 동적으로 기능을 변경할 수 있다.
  4. 각 장식자 클래스마다 고유의 책임을 가져 단일 책임 원칙(SRP)Visit Website을 준수
  5. 클라이언트 코드 수정없이 기능 확장이 필요하면 장식자 클래스를 추가하면 되니 개방 폐쇄 원칙(OCP)Visit Website을 준수
  6. 구현체가 아닌 인터페이스를 바라봄으로써 의존 역전 원칙(DIP)Visit Website 준수

단점

  1. 만일 장식자 일부를 제거하고 싶다면, Wrapper 스택에서 특정 wrapper를 제거하는 것은 어렵다.
  2. 의미없는 객체들이 너무 많이 추가될 수 있다.
  3. 데코레이터를 너무 많이 사용하면 코드가 필요 이상으로 복잡해질 수 있다.
  4. 데코레이터를 조합하는 초기 생성코드가 보기 안좋을 수 있다. new A(new B(new C(new D())))
  5. 어느 장식자를 먼저 데코레이팅 하느냐에 따라 데코레이터 스택 순서가 결정지게 되는데, 만일 순서에 의존하지 않는 방식으로 데코레이터를 구현하기는 어렵다.


참고


[디자인패턴] 디자인패턴이란? - 생성패턴, 구조패턴, 행위패턴

데코레이터(Decorator) 패턴 - 완벽 마스터하기

profile
개발공부를해보자

0개의 댓글