디자인 패턴 101 - DECORATOR

subutai·2021년 6월 7일
0

디자인 패턴 101

목록 보기
3/3

🐱‍🏍 Decorator

의도

객체에 동적으로 새로운 책임을 추가할 수 있게 합니다.
기능을 추가하려면, 서브클래스를 생성하는 것보다 융통성 있는 방법을 제공합니다

풀어써본 의도

핵심은 '동적으로 새로운 책임을 추가' 에 있다고 생각합니다.
즉, 런타임에 다형성을 이용하여 다양한 책임의 조합을 만들어낼 수 있는데 의의가 있습니다.
책임을 추가한다는게 무엇인지, 런타임에 책임을 추가하는게 왜 좋은지는 예제를 보는게 제일 좋을 것 같습니다.

저희가 자동차 회사의 견적 시스템을 설계하는 개발자라고 생각해볼께요.

고객이 원하는 옵션을 선택하면, 옵션을 조합했을 때 보여지는
내/외부의 모습을 시뮬레이션해 보여주는것이 핵심기능입니다.

고객은 아래의 옵션들을 선택할 수 있습니다.

  1. 외부 도색
    • 일반 도색
    • 무광 도색
    • 크롬 도색
  2. 내부 시트종류
    • 인조 가죽
    • 천연 가죽
  3. 기능 옵션 (각 옵션이 뭔지 알 필요는 없습니다. 저도 차를 안 사봐서 검색해서 가져왔어요..)
    • ABS 시스템
    • AEB 시스템
    • 크루즈 컨트롤

이뿐만 아니라, 옵션은 끊임없이 추가될 수 있습니다.
만약에 무광 도색 + 인조 가죽 + 크루즈 컨트롤 이 적용된 자동차 클래스를 만든다면 아래와 같겠죠?

class Car {} 
class MattPaintingArtificalLeatherCruiseControlCar extends Car {} 

음.. 현재 알려진 옵션만해도, 이러한 곤란한 클래스가 3X2X3개 = 18개가 생기게 되겠습니다.
분명히 뭔가 잘못된것을 느낄수 있는데요. 이러한 현상을 '조합폭발' 이라고 합니다.
다양한 조합의 책임을 가진 클래스들이 엄청나게 많이 생기는것이죠

여기까지보고 다시 고리타분한 의도로 돌아가볼께요.
여기서 책임은 각 옵션을 의미합니다. (차에 새로운 기능/외관을 추가하는 책임이죠)
런타임에 추가한다는 말은, 무광 도색 + 인조 가죽 + 크루즈 컨트롤 같은 복잡한 조합의 타입을
코드에 정적으로 명시하는 것이 아니라, 동일한 타입(이게바로 Decorator!)을 런타임에 조합해서 추가한다는 의미입니다.

그러면, Decorator 패턴을 적용하여 개선한 설계를 보겠습니다

class Car { 
	public void simulate() {
		// ... 속성들을 이용해 GUI 캔버스에 차를 그린다 
	}
}

abstract class CarDecorator extends Car {
    private Car decoratedCar;
    
    public CarDecorator(Car car) {
    	this.car = decoratedCar;
    }
    
	public void simulate() {
    	decoratedCar.simulate();
    }
} 

class MattPaintingDecorator extends CarDecorator {
	
    public MattPaintingDecorator(Car car) {
    	super(car);
    }
    
    public void paintMatt() {
    	System.out.println("무광도색!");
    }
    
    public void simulate() {
    	super.draw();
        this.paintMatt();
    }
}

class ArtficialLeatherDecorator extends CarDecorator {
	
    public ArtficialLeatherDecorator(Car car) {
   	super(car);
    }
    
    public void coverArtificalLeather() {
    	System.out.println("인조가죽 사용!");
    }
    
    public void simulate() {
    	super.draw();
        this.coverArtificalLeather();
    }
}

class CruiseControlDecorator extends CruiseControlDecorator {
	
    public CruiseControlDecorator(Car car) {
    	super(car);
    }
    
    public void installCruiseControl() {
    	System.out.println("크루즈컨트롤 탑재!");
    }
    
    public void simulate() {
    	super.draw();
        this.installCruiseControl();
    }
}

// 클라이언트 코드
public class Main {
	public static void main(String[] args) {
    	// 무광도색 + 인조가죽 + 크루즈컨트롤 탑재 자동차!
    	Car MattPaintingArtificalLeatherCruiseControlCar
        = new CruiseControlDecorator(new ArtificialLeatherDecorator(
                    			new MattPaintingDecorator(new Car())));
             
    }
}

데코레이터 패턴을 적용해봤는데 어떤가요? 개인적으로 패턴중에 가장 극적이고 감동적이라고 생각합니다!
이제 어떠한 옵션을 조합하더래도, 새로운 클래스를 정적으로(코드) 만들어주는것이 아닌
'다양한 데코레이터들을 적절히 조합' 해서 만들어주면 되는겁니다!

오브젝트(조영호 저)에서는 데코레이터 패턴을

DECORATOR 패턴은 선택적인 행동의 개수와 순서에 대한 변경을 캡슐화 할 수 있다.

라고 말하는데요, 여기서 '순서' 가 데코레이터 패턴이 더 흥미로워지는 부분인데요

세금계산을 하는 프로그램을 설계해야한다고 생각해봤을 때
가장 골치아플것이라 예상되는 부분은, 차감/차증(差增)의 순서를 정해주는 것인데요

납세자가 내야하는 법인세가 1000만원입니다.
A세제 혜택으로 법인세율 15%를 감면받고
B세제 혜택으로 50만원을 감면받을 수 있을때

1000만원을 A->B 순서로 감면받아서 8,000,000원을 내야하는지
1000만원을 B->A 순서로 감면받아서 8,075,000원을 내야하는지

세금 규정에 따라서 프로그램을 만들어주어야 하는데
각 각의 규정의 조합을 클래스로 만든다고 생각하면.. 언뜻 생각해봐도 불가능할 것 같습니다.
수 만 개의 세금규정을 조합하면 천문학적인 갯수의 조합이 나올테니까요.

그래서 이 경우에 A,B를 데코레이터로 만들어주어서

TaxCalc AfirstTax = new ApolicyDecorator(new BpolicyDecorator(TaxCalc());
TaxCalc BfirstTax = new BpolicyDecorator(new ApolicyDecorator(TaxCalc());

규정에 따라 적절한 방식으로 '순서'를 정해줄 수 있겠죠?

다른 세금헤택도 마찬가지로, Decorator 로 만들어서 조합해주면 되겠습니다.

특징

1. 상속보다 설계의 융통성을 더 많이 증대시킬 수 있습니다
클래스에 새로운 책임 추가/삭제하는 일이 데코레이터 객체를 사용하므로써 런타임에 가능해집니다.
데코레이터를 사용하면 데코레이터의 조합을 통해서 새로운 책임도 조합할 수 있습니다

2. 클래스 계통의 상부측 클래스에 많은 기능이 누적되는 상황을 피할 수 있습니다
데코레이터 패턴은 책임 추가 작업에서 "필요한 비용만 그때 지불하는" 방법을 택합니다

앞의 에제에서 각 옵션들(무광, ABS시스템, 인테리어)등이 필요할때만 추가하면되지
구체적인 자동차 객체가 그것들에 대해서 가지고 있지 않습니다!
단지 필요할때만 추가해서 쓸 뿐

즉! 개발 시 현재 사용되지 않는 기능까지 개발하기 위해 시간과 노력을 투자할 필요가 없다!

3. 데코레이터를 사용함으로써 작은 규모의 객체들이 많이 생깁니다
오브젝트(조영호 저)에 적혀있던 문구중에 인상깊었던 것은 '설계는 트레이드 활동이라는 것을 기억하라' 였습니다.
객체지향 설계와 디자인 패턴이 궁극적으로 추구하는 바는, 응집력 있는 객체들의 협력을 통한 설계이고
이러한 방식의 설계를 적용하게 되면, 프로그램이 커질수록 많은 객체들이 생기게 됩니다.

즉, 객체지향적인 설계가 될수록 클래스가 많아지고, 객체들간의 관계파악에 시간이 오래걸리죠.
하지만, 대신에 유연하고 확장성 있는 구조가 갖춰지게 됩니다.

데코레이터 패턴을 적용하지 않는다면
1. 규모의 객체들이 많이 생기는(조합폭발)현상이 발생하거나
2. 하나의 클래스에 지나치게 많은 책임이 모이는 현상이 발생합니다

하지만, 데코레이터 패턴을 적용하면 자그마한 Decorator 객체들이 많이 생기게 되죠

결론

  1. 데코레이터 패턴은 다양한 책임을 정적으로 조합하는데 생기는 '조합폭발'현상을 해결한다

  2. 데코레이터 패턴은 객체의 책임을 동적으로 추가하고
    책임의 적용 방법/순서를 캡슐화할 수 있는 디자인패턴이다

0개의 댓글