OOP/Design Pattern

[OOP] 데커레이터 패턴( Decorator Pattern )

IT록흐 2023. 3. 11. 12:22
반응형

디자인 패턴이란?

객체지향설계 과정에서 발생하는 문제들을 해결하기 위한 패턴(Pattern)

 

문제 상황

기본 기능이 있고 옵션 기능이 여러 개일 때,  기본 기능 + 옵션 기능의 조합은 여러 가지이다. 예를 들어, 내비게이션이 있다. 내비게이션의 기본기능은 도로표시이다. 옵션 기능으로 교통량 표시, 날씨 표시, 메시먼지 표시가 있다고 해보자. 메인 기능 1가지에 옵션 기능 3가지가 있으므로, 총 9가지 조합이 생긴다. 

 

문제는 이를 단순 상속으로 구현하려고 하면 9가지를 전부 클래스로 만들어야 한다는 것이다. 

 

 

 

 

단순하게 부모로부터 기능을 상속받는 구조는 조합의 개수에 따라 구현해야하는 클래스 개수를 기하급수적으로 증가시킨다. 그래서 이런 문제를 해결하기 위한 디자인 패턴이 있으니, 그것이 데코레이터 패턴이다. 

 

 

해결책

 

Decorate는 '장식하다'를 의미한다. 기본기능만 있는 Navi 객체에 여러 기능을 장식하려고 한다.  데코레이터 패턴은 최소한의 클래스 개수로 장식할 수 있다. 데코레이터 패턴은 핵심원리는  합성관계(Composition)를 이용한 패턴이다. 

 

 

 

합성관계란 한 객체가 다른 객체의 멤버변수가 되는 연관관계인데, 특징은 생명주기를 같이 한다는 것이다. 

 

 

public class Computer {

	private Monitor monitor; // 멤버변수
    
    public Computer(){
    	monitor = new Monitor(); // 생명주기를 같이 함
    }
}

 

 

Computer 생성자에서 Monitor가 생성되기에, Computer와 Monitor는 서로 독립된 객체가 아닌 Computer가 전체, Monitor가 부분이 되는 종속된 관계이다. Computer 객체가 사라지면 Monitor 객체도 사라진다. 

 

이와 같은, 합성관계는 여러 개의 조합을 생성할 수 있다. 

 

 

// 최소기능 (도로표시기능)
public class Navi {

    public Navi(){}

    public void drawRoad(){}
}


// 교통량 추가된 기능
public class NaviWithTraffic {
    private Navi navi;

    public NaviWithTraffic(){
        navi = new Navi(); // 도로표시 기능 합성 시키기
    }

    public void drawRoad(){
        navi.drawRoad(); // 도로표시 
        showTraffic(); // 교통량 표시 추가 
    }
    
    public void showTraffic(){}
}

 

NaviWithTraffic 클래스는 Navi 클래스를 합성하여  "최소기능 + 추가기능"하여 복합기능이 가능해졌다.  이제 이를 한번 더 합성해보자. 

 

// 날씨표시 추가된 기능
public class NaviWithWeather {
    private NaviWithTraffic naviWithTraffic;

    public NaviWithWeather(NaviWithTraffic naviWithTraffic) {
        this.naviWithTraffic = naviWithTraffic; // ( 도로표시 + 교통량 ) 기능 합성시키기 
    }
    
    public void drawRoad(){
        naviWithTraffic.drawRoad(); // 도로표시 + 교통량
        showWeather(); // 날씨 기능 추가
    }
    public void showWeather(){}
}

 

 

NaviWithWeather 클래스는 naviWithTraffic클래스를 합성하여  "도로표시+ 교통량 + 날씨표시"하여 복합기능이 가능해졌다.  

 

 

 

 

이처럼 합성관계를 이용하면 최소기능에서 부가기능이 추가된 복합기능 클래스를 조합하여 만들어 낼 수 있다. 하지만 위 코드에는 문제점이 하나 있다. Navi -> NaviWithTraffic에 NaviTraffic->NaviWithWeather에 종속되어 버린다. 종속을 제거하고 다양한 조합이 가능하도록 하려면 일련의 구조가 필요한다. 

 

 

 

 

 

코드

 

최소기능 추상클래스( Display )

public abstract class Display {
    public abstract void draw();
}

 

최소기능 Display 클래스 (도로표시)

public class RoadDisplay extends Display{
    @Override
    public void draw() {
        System.out.println("최소기능 : 도로표시");
    }
}

 

여기가 가장 기본이 되는 시작점이다. 이제 합성관계를 이용하여 장식(데코레이트)을 해보자.

 

 

데코레이터

public class DisplayDecorator extends Display{

    public Display decoratedDisplay; // 합성된 Display

    public DisplayDecorator(Display decoratedDisplay) {
        this.decoratedDisplay = decoratedDisplay; // 기능 합성시키기
    }
    
    @Override
    public void draw() {
        decoratedDisplay.draw(); // 합성된 기능 호출
    }
}

 

부가기능 데코레이터

//교통량 데코레이터
public class TrafficDecorator extends DisplayDecorator{
	
    public TrafficDecorator(Display decoratedDisplay) {
        super(decoratedDisplay); // DisplayDecorator의 멤버변수 초기화
    }

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

    public void addDraw(){
        System.out.println("교통량 표시");
    }
}
//날씨 데코레이터
public class WeatherDisplay extends DisplayDecorator {

    public WeatherDisplay(Display decoratedDisplay) {
        super(decoratedDisplay); //Display 멤버변수 초기화
    }

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

    public void addDraw() {
        System.out.println("날씨 표시");
    }
}

 

데코레이터(DisplayDecorator)에 Display 참조변수가 있다. 데코레이터를 상속하는 부가기능 데코레이터들은 super()를 사용하여 Display를 초기화한다. 초기화되면 초기화 할수록 점점 복합기능을 가진 Display가 된다. 

 

Main ( 클라이언트 )

public class DecoratorPatternMain {

    public static void main(String[] args) {

        Display weatherDisplay = new WeatherDisplay(new TrafficDecorator(new RoadDisplay())); // 합성시키기
        weatherDisplay.draw();

    }
}

 

가장 기본 함수인 RoadDisplay로 시작해서 Traffic 그리고 Weather로 점점 기능을 합성시킨 모습이다.

 

생성자 인수에 new 연산자를 통해 객체를 생성하여 합성관계를 형성한다. new 연산자를 통해 객체가 생성되면 생성될수록 super()로 인해 더 복합적인 기능을 가진 Display가 만들어진다.

 

 


 

정리 

 

 

데코레이터 패턴이 없다면, 3가지 기능을 가진 9가지 조합을 모두 클래스로 만들어야 한다. 4가지 기능이면 16가지이고 5가지이면 25가지 클래스를 구현해야한다. 데코레이터 패턴을 이용하면 개발자는 기능별 데코레이터만 준비하면 된다. 조합은 클라이언트가 입맛에 맞게 합성관계를 만들면 된다.

 

그러므로 데코레이터 패턴은 기본기능에 여러기능을 추가하는 상황에서 사용가능한 적절한 패턴이라고 할 수 있다.

 

 

 


 

 

참고자료

 

JAVA 객체지향 디자인 패턴 : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

상속보다는 컴포지션을 사용하자

1. 상속과 컴포지션 상속 하위 클래스가 상위 클래스의 특성을 재정의 한것 > (IS-A) 관계 컴포지션 기존 클래스가 새로운 클래스의 구성요소가 되는것 > (HAS-A) 관계 자바에서는 부모 클래스의 메

dev-cool.tistory.com

 

반응형