본문 바로가기
[CS] - Design Pattern

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

by Bebsae 2020. 12. 28.

데코레이터 패턴은 기능을 동적으로 확장할 때 유용한 패턴이다.

 

예를 들어보자. A라는 기본기능과 B, C, D라는 추가 기능을 조합할 경우, 경우의 수는 

 

A

A + B

A + C

A + D

A + B + C

A + B + D

A + C + D

A + B + C + D

 

8가지의 경우가 나온다. 그럼 무식하게  메소드만을 활용하여 (상속을 통해) 8가지의 클래스를 정의해서 사용할 것인가? 최악의 경우를 생각해보자. 추가 기능이 1억개면? 답이 없다..

 

그래서 메소드가 아닌 인스턴스의 조합을 활용하면 어떨까? 라는 생각에서 나온것이 데코레이터 패턴이다. 

 

데코레이터 패턴 클래스 다이어그램

 

첨언을 하자면, DefaultBehavior 클래스는 기본기능이라고 생각하자.

public abstract class Base {
    public abstract void show();
}
public class DefaultBehavior extends Base {
    @Override
    public void show() {
    	System.out.println("show base");
    }
}

 

 여기에 추가 기능들(A, B, C)을 일반화한 클래스가 BehaviorDecorator이다. 

public class BehaviorDecorator extends Base {
    private Base decorated_behavior;
    public BehaviorDecorator(Base decorated_behavior) {
        this.decorated_behavior = decorated_behavior; // 자식 클래스로부터 받은 Base 인스턴스 등록
    }
    
    @Override
    public void draw() {
        decorated_behavior.draw();
    }
}
public class ABehavior extends BehaviorDecorator {
    public ABehavior(Base decorated_behavior) { // 인자에 Base 인스턴스가 들어오고..
        super(decorated_behavior); // 해당 인스턴스가 부모클래스(BahaviorDecorator)에 등록된다.
    }
    
    @Override
    public void show() {
        super.show(); // 부모클래스(BehaviorDecorator)에 등록된 Base 인스턴스의 show() 호출.
        showA(); // 그 다음 자기의 showA() 호출.
    }
    
    private void showA() {
        System.out.print("showA");
    }
}
public class BBehavior extends BehaviorDecorator {
    public BBehavior(Base decorated_behavior) {
        super(decorated_behavior);
    }
    
    @Override
    public void show() {
        super.show();
        showB();
    }
    
    private void showB() {
        System.out.print("showC");
    }
}
public class CBehavior extends BehaviorDecorator {
    public CBehavior(Base decorated_behavior) {
        super(decorated_behavior);
    }
    
    @Override
    public void show() {
        super.show();
        showC();
    }
    
    private void showC() {
        System.out.print("showC");
    }
}

 

이제 클라이언트에서는 어떻게 처리하는지 확인해보자.

public class Client {
    public static void Main(String[] args) {
        Base behavior_A_B_C = 
            new CBehavior(
            new BBehavior(
            new ABehavior(
            new DefaultBehavior())));
            
        behavior_A_B_C.draw();
    }
}

 

작동되는 플로우를 생각해보자.

 

인스턴스 등록

1. DefaultBehavior 인스턴스가 ABehavior 인스턴스의 부모(BehaviorDecorator)에 등록된다.

2. ABehavior 인스턴스가 BBehavior 인스턴스의 부모(BehaviorDecorator)에 등록된다.

3. BBehavior 인스턴스가 CBehavior 인스턴스의 부모(BehaviorDecorator)에 등록된다.

 

super.show()

최종적으로 CBehavior 인스턴스의 draw()를 호출하게 되면,

CBehavior의 부모에 등록된 인스턴스는 BBehavior 인스턴이다. BBhavior 인스턴스의 show()를 호출하게 된다.

BBehavior의 부모에 등록된 인스턴스는 ABehavior 인스턴이다. ABhavior 인스턴스의 show()를 호출하게 된다.

ABehavior의 부모에 등록된 인스턴스는 DefaultBehavior 인스턴이다. DefaultBhavior 인스턴스의 show()를 호출하게 된다.

 

show()

그다음 각 클래스의 super.show()가 모두 호출되었으므로, showA(), showB(), showC()가 순서대로 호출될 것이다.

 

즉, 우리는 경우의 수에 따라 클래스의 조합을 정의하는 것이 아닌

필요에 따라 클라이언트에서 객체들을 조합하여 코드를 수정하지 않고 (OCP) 기능들을 조합할 수 있다.

댓글