문제 제기

- 우리가 스타** 커피집을 차렸다고 생각해보자.

 

-장사 초기에는 POS에서 구현된  음료객체는 많지않았고, 위와같이 아주 심플했다.

 

-그러나 스팀 우유나, 두유, 초콜릿을 추가하고 그위에 휘핑크림을 얹기도 하면서 굉장히 많은 메뉴들이 생겨나기 시작했다.

-주문시스템에서도 이러한 메뉴들을 반영하기 시작했고 초기에는 무조건 상속받아 구현하였다.

- 앞으로 녹차가 추가될수도있고 많은 토핑들이 추가될수도있다. 그러면 이렇게 클래스가 늘어나는 것을 지켜보고만 있어야할까?

- 이 스토리에서는 앞서 배운 두가지 디자인패턴 원칙을 따르지 않고있다.

 

- 그러면 폭팔적으로 늘어나는 음료 class를 막기위해 다음과같이 super 클래스를 정의해보면 어떨까?

-각 cost() 에서는 일단 음료의 가격을 구한다음, 첨가된 항목에따라 추가 가격을 더하면 된다.

-그러나 여기서 야기될 수 있는 문제점이 몇가지 보인다.
 1) 첨가물 가격이 바뀔때마다 기존코드 수정.
 2) 첨가물의 종류가 추가되면 setter메소드 및 has 메소드 추가
 3) 새로운 음료가 출시됐는데 거기에 기존 첨가물이 안들어가도 오버라이드 되어야함. (녹차에 whip 등)
 4) 손님이 더블 휩을 주문한다면? boolean에서 int로 설계해야 할까?

- 서브클래스를 만드는 방식으로 행동을 상속받게되면, 그 행동은 컴파일시에 완전히 결정되어진다.
-> 반면 구성을 통해서 객체의 행동을 확장하면 실행중에 동적으로 행동을 설정할 수 있다.
 또한, 기존의 코드는 건드리지않기 때문에, 기존코드에서 버그가 생기거나 의도하지 않은 부작용이 발생하는 것을 원천 봉쇄할 수 있다. 

디자인 원칙
클래스는 확장에 대해서는 열려 있어야 하지만, 코드변경에는 닫혀있어야 한다.

- 이 원칙을 준수하기 위해서는, 바뀔 확률이 높은 부분을 중점적으로 살펴보고, 원칙을 적용하는 방법이 가장 현명하다.
(무조건 OCP(Open Closed Principale)를 적용하는 것은 시간낭비가 될 수도있고, 쓸데없이 일을 크게 벌일 수도 있으니 유의하자)

 

데코레이터 패턴의 개념 추상화

-상속을 써서 음료의 가격과 첨가물(휘핑,우유,모카)의 총 가격을 계산하는 것은 그닥 좋지 않았다. 음료를 첨가물로 Decroeate 해보면 어떨까?
 1) DarkRoast 객체를 가져온다.
 2) Mocha 객체로 장식한다.
 3) Whip 객체로 장식한다.
 4) cost() 메소드를 호출한다. 이때 첨가물의 가겨을 계산하는 일은 해당 객체들에 위임된다.

 

-위 로직을 그림으로 표현해보자.

1) DarkRoast 객체를 가져온다.

 

 

 2) Mocha 객체로 장식한다.

 


 3) Whip 객체로 장식한다.

 

 


 4) cost() 메소드를 호출한다. 이때 첨가물의 가겨을 계산하는 일은 해당 객체들에 위임된다.

 

데코레이터 패턴의 정의

데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있다.

- 데코레이터 패턴의 추상화된 클래스 다이어그램부터 살펴보자.

- 조금은 복잡하지만, 원래 Super 클래스를 상속받아, 구체화 시킨 class를 Decorator의 내부 인스턴스로 가지고있다는 것 밖에 없다.

 

드디어 우리 카페에 Decorator 패턴을 적용시켜보자.

 

 

- 자 이제 코드로 구현해보자, 추상 클래스들(음료, 데코레이터)를 구현해보자

public abstract class Beverage {
	String description = "제목 없음";
	
	public String getDescription() {
		return description;
	}
	
	public abstract int cost();

}

 

//Beverage 객체가 들어갈 자리에 있어야하므로, 베버리지 상속
public abstract class ToppingDecorator extends Beverage {
	//모든 토핑은 description을 구현하도록 만들예정임
	public abstract String getDescription();
}

 

- 다음은 구현체들을 만들어보자.

public class DarkRoast extends Beverage{

	public DarkRoast() {
		this.description = "다크로스트";
	}
	
	@Override
	public int cost() {
		return 3000;
	}
}

 

- 아래부터는 토핑 데코레이터의 구현체이다.

public class Mocha extends ToppingDecorator{
	
	Beverage beverage;
	
	public Mocha(Beverage beverage) {
		this.beverage = beverage;
	}
	
	@Override
	public String getDescription() {
		return beverage.getDescription() + ", 추가 모카";
	}

	@Override
	public int cost() {
		return 500 + this.beverage.cost();
	}

}

 

public class Whip extends ToppingDecorator{

	Beverage beverage;
	
	public Whip(Beverage beverage) {
		this.beverage = beverage;
	}
	
	@Override
	public String getDescription() {
		return beverage.getDescription()  + ", 추가 휘핑";
	}

	@Override
	public int cost() {
		return 500 + beverage.cost();
	}

}

 

 

- 이제 데코레이터들을 통해 음료를 wrapping 하는법을 알아보자.

public class CafeMain {

	public static void main(String[] args) {
		Beverage beverage = new DarkRoast();
		
		System.out.println(beverage.getDescription() + " 가격은: " + beverage.cost());
		
		//모카 추가
		beverage = new Mocha(beverage);
		System.out.println(beverage.getDescription() + " 가격은: " + beverage.cost());
		
		//휘핑 추가
		beverage = new Whip(beverage);
		System.out.println(beverage.getDescription()  + " 가격은: " + beverage.cost());
		
	}

}

 

-결과는 우리가 바라던 대로 잘 나온다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

5.Singleton Pattern  (0) 2020.01.07
4-2 Factory Pattern (Abstract Factory Pattern)  (0) 2020.01.05
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
2.Observer Pattern  (0) 2020.01.01
1.Strategy Pattern  (0) 2019.12.31

+ Recent posts