데코레이터 패턴

seanical·2022년 2월 18일
0

java

목록 보기
3/3

서론


데코레이터의 의미는 장식하다, 꾸미다입니다

따라서 파이썬에서는 데코레이터를 함수 실행 전 후에 사용할 수있게 함수를 꾸며주고합니다.

전혀 다르지만 의미가 비슷하게 사용되는 자바에서는 데코레이터 패턴이 있습니다.

예시를 들어 자동차 네비게이션에서 도로를 표시하는 기능(단속 카메라위치, 속도제한)을 켜두자고 생각합시다.



public class RoadDisplay {

    public void draw() { System.out.println("기본 도로 표시"); }
}

또 사용자가 차선 표시도 하고싶어 기능을 키고싶으면 어떻게 할까요?

public class RoadDisplayWithLane extends RoadDisplay {
    public void draw() {
        super.draw();
        drawLane();
    }
    
    private void drawLane() {
        System.out.println("차선 표시");
    }
}

RoadDisplay 를 상속받아서 차선 표시기능을 추가 할 수 있습니다.

근데 만약 주위 교통량을 살피기 위한 기능을 추가해본다고 생각해봅시다.
RoadDisplay를 상속하여 새로운 클래스를 만들어야합니다.

이렇게 하다보면 모든 필요한 기능의 모든 조합의 클래스를 생성해야하는 문제를 마주합니다.

이런 불필요한 코드를 없애기위해 데코레이터 패턴이 등장했습니다.

본론


위 속성을 살펴보면 디스플레이에 표시한다라는 것이 공통점입니다.

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

해당 클래스를 추상클래스로 만들어줍니다.

가장 기본적 기능을 명시하는 추상클래스를 Component라고 명칭합니다.

컴포넌트(Component)란 프로그래밍에 있어 재사용이 가능한 각각의 독립된 모듈을 뜻한다.


그리고 이 Component의 실체를 구현할 것을 명시합니다.

public class RoadDisplay extends Display{

    @Override
    public void draw() {
       System.out.println("기본 도로 표시");
    }
}

위의 역할을 ConcreteComponent 명칭합니다.
실제 구현체 역할을 합니다.

ConcreteComponent: 독립된 모듈의 구현체


이제 Decorator 즉, 무언가 꾸미는 역할을 연결해주는 Class를 생성해봅시다.

package decorator;

public class DisplayDecorator extends Display{
    private Display decoratedDisplay;

    public DisplayDecorator(Display decoratedDisplay) {
        this.decoratedDisplay = decoratedDisplay;
    }

    @Override
    public void draw() {
        this.decoratedDisplay.draw();
    }
}

최상위 Display를 setter로 주입을 받아 받아온 Display(추상화) 정확히 말하면 Display의 구현체를 가져와 함수를 실행합니다.

객체를 주입하고 , 주입된 객체의 함수를 호출하는 것이 핵심입니다.

위 역할을 말 그대로 Decorator라고 명칭합니다.


이제 사용자가 차선을 표시하고싶다 하는 위 예제를 적용해봅시다.


public class LaneDecorator extends DisplayDecorator{

    public LaneDecorator(Display decoratedDisplay) {
        super(decoratedDisplay);
    }

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

    private void drawLane() {
        System.out.println("차선표시");
    }
}

생성자를 상속받는DisplayDecorator생성자를 호출함으로써 객체를 주입하는 것입니다.
그리고 자신의 draw()를 재정의 함으로써 자신의 로직을 추가하면됩니다.

이런것을 ConcreteDecorator라고 부릅니다.
자신의 할일을 추가적으로 구현하여 public void draw()super.draw()의 앞뒤로 함수를 decorate(꾸며주기)하면 됩니다.

왜 이제 Decorate가 붙었는지 이해가 되시나요!?

클라이언트에서 호출하는 코드를 작성해보면 이렇습니다.

        Display roadWithLine = new LaneDecorator(new RoadDisplay());
        roadWithLine.draw();
        
        // 기본 도로 표시 
        // 차선표시

만약 위의 트래픽기능을 추가하고싶으면 어떨까요?

public class TrafficDecorator extends DisplayDecorator{
    public TrafficDecorator(Display decoratedDisplay) {
        super(decoratedDisplay);
    }

    @Override
    public void draw() {
        super.draw();
        drawTraffic();
    }
    private void drawTraffic() {
         System.out.println("트래픽그리기");
    }
}

위와 같이 ConcreteDecorator의 역할을 하는 것을 추가해주면됩니다.

그렇다면 총 3가지 기능을 갖는 트래픽을 보여주고, 기본 도로를 표시해주고, 차선을 표시해주는 기능을 호출하려면 다음과 같습니다.

        Display trafficRoadWithLine = new TrafficDecorator(new LaneDecorator(new RoadDisplay()));
        trafficRoadWithLine.draw();
        //기본 도로 표시
        //차선표시
        //트래픽그리기 
 

이제는 여러가지의 조합의 클래스를 만들어 주지 않아도 Decorator를 통해 코드의 낭비를 줄이고 더 가독성도 늘어난 코드를 사용할수 있습니다.

정리


Component

  • Display 클래스 (draw 추상적 개념)

ConcreteComponent

  • RoadDisplay클래스 (Display 실질적 구현)

Decorator

  • DisplayDecorator 클래스 (Component와 ConcreteDecorator 연결 역할)

ConcreteDecorator

  • TrafficDecorator 클래스, LaneDecorator 클래스 (추가된 책임 로직 역할)

0개의 댓글