03. 데코레이터 패턴

Mando·2023년 3월 17일
0

디자인 패턴

목록 보기
3/6
post-thumbnail

커피 주문 시스템의 문제점 파악하기

Beverage는 음료를 나타내는 추상 클래스이며 매장에서 판매되는 모든 음료는 이 클래스의 서브 클래스가 된다.
이중 cost는 추상메서드로 서브 클래스에서 이 메서드를 구현해야 한다.

문제점은 다음과 같다.
음료를 주문할 때 우유나, 모카, 두유 등 다양한 옵션을 추가하는데
이때마다 옵션에 따라 Beverage 클래스를 새로 만들어야 하는가?
또한 각 첨가물의 가격이 변한다면(예를 들어 우유의 가격이 변한다면) 우유를 포함하고 있는 음료들을 하나씩 찾아서 가격을 변경해주어야 하는가?

서브 클래스를 사용해 리팩터링

옵션에 따라 Beverage 클래스가 너무 많이 생긴다
클래스 수를 줄이는 방법을 생각해보자

인스턴스 변수와 슈퍼 클래스 상속을 이용해 첨가물 관리하기

  • 첨가물에 해당하는 boolean 인스턴스 변수
  • cost는 첨가물의 비용을 계산한다.
  • 하지만 서브 클래스는 cost를 오버라이딩 해야 한다. 서브 클래스에서 각 음료의 가격을 계산한 다음 슈퍼 클래스의 cost를 호출함으로써 첨가물 비용에 음료 가격을 더할 수 있다.

문제점

  1. 첨가물 가격이 바뀌면 Beverage의 인스턴스 변수를 변경해야 한.
  2. 첨가물이 추가되면 기존 코드를 수정해야 한다.(인스턴스 변수 추가, cost의 가격계산 변경)
  3. 새로운 음료가 출시되었다. 현재 설계에서는 Beverage를 상속받는 모든 음료 클래스들은 Beverage에 있는 모든 첨가물을 추가할 수 있게 된다. 하지만 우리는 특정 음료에서는 특정 첨가물을 제외하고 싶다.
  4. 첨가물을 여러 번 주문할 수가 없다.

상속보다 구성?

  1. 서브클래스를 만드는 방식으로 행동을 상속받으면 그 행동은 컴파일시에 완전히 결정된다.
  2. 모든 서브클래스는 똑같은 행동을 상속받아야 한다.(특정 서브클래스는 슈퍼 클래스의 특정 행동을 상속받지 않는 것이 불가능하다.)

하지만, 구성으로 객체의 행동을 확장하면 실행 중에 동적으로 행동을 변경할 수 있다.

객체룰 행동을 동적으로 변경할 수 있는 것의 장점은 다음과 같다.
기존 코드를 건드리지 않고 확장으로 새로운 행동을 추가할 수 있다.

OCP(Open Closed Principal)
클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.

데코레이터 패턴을 적용한 주문 시스템 설계

특정 음료에서 시작해 첨가물로 그 음료를 장식(decorate) 해보자

에를 들어 어떤 고객이 모카와 휘핑 크림을 추가한 다크 로스트 커피를 주문한다면 다음같이 데코레이터 패턴을 적용할 수 있다.

  1. DarlRoast 객체를 가져온다.
  2. Mocha 객체로 장식한다.
  3. Whip 객체로 장식한다.
  4. cost()메서드를 호출한다. 이때 첨가물의 가격을 계산하는 일은 해당 객체에게 위임한다.

이때 객체를 어떻게 장식할 수 있을까?
또한 어떤 식으로 각 객체에게 가격을 계산하는 책임을 위임할 수 있을까?

  1. DarkRoast객체는 Beverage로부터 상속받으므로 가격을 계산하는 cost()메서드를 가지고 있다.
  2. Mocha 객체는 데코레이터이다. 객체의 형식은 데코레이터가 장식하고 있는 객체와 같은 형식을 같는다. 따라서 Mocha도 Beverage의 서브 클래스로 cost()메서드를 가지고 있다.
  3. Whip 객체도 데코레이터이다.

데코레이터 패턴의 정의

데코레이터 패턴이란?
객체에 추가 요소를 동적으로 저할 수 있다. 따라서 데코레이터를 사용하면 서브 클래스를 만들 때보다 훨씬 더 유연하게 기능을 확장할 수 있다.

1. 구상 구성 요소를 감싸 주는 데코레이터를 사용한다
2. 데코레이터 클래스의 형식은 그 클래스가 감싸는 클래스 형식을 반영한다.
3. 데코레이터는 감싸고 있는 구성요소의 메소드 호출결과에 새로운 기능을 더해 행동을 확장한다.

데코레이터 패턴의 장점

OCP를 준수하는, 확장엔 열려있고 변경엔 닫힌 멋진 코드를 짤 수 있습니다!

데코레티어 패턴의 단점

클래스가 많아진다
클래스의 구성을 파악하기 힘들다
구성 요소의 초기화 코드가 복잡해진다 ! 팩토리 및 빌더 패턴으로 개선 가능

데코레이터 패턴을 적용한 주문 시스템 구현

추상 구성요소

public abstract class Beverage {
	String description = "제목 없음";

	public String getDescription() {
		return description;
	}

	public abstract double cost();
}

추상 데코레이터

// Beverage 객체를 감쌀 수 있도록 Beverage 클래스를 확장
public abstract class CondimentDecorator extends Beverage { 
	Beverage beverage;

	public abstract String getDescription();
}

구상 구성요소

public class Espresso extends Beverage {
	
	public Espresso() {
		description = "에스프레소";
	}
	
	public double cost() {
		return 1.99;
	}

}

구상 데코레이터

public class Mocha extends CondimentDecorator {
	
	// 인스턴스 변수를 감싸고자 하는 객체를 받음
	public Mocha(Beverage beverage) {
		// 감싸고자 하는 음료를 저장하는 인스턴스 변수
		this.beverage = beverage; 
	}

	public String getDescription() {
		return beverage.getDescription() + ", 모카";
	}

	public double cost() {
		return beverage.cost() + .20;
	}

}

주문시스템 테스트 코드

public class StarbuzzCoffee {
	
	public static void main(String args[]) {
		Beverage beverage = new Espresso();

		Beverage beverage2 = new Espresso();
		beverage = new Mocha(beverage); // Mocha로 감싼다
		beverage = new Mocha(beverage); // Mocha로 감싼다
		beverage = new Whip(beverage); // Whip으로 감싼다

		System.out.println(beverage.getDescription() + " $" + beverage.cost());
		// 에스프레소 커피 $1.99
		System.out.println(beverage.2getDescription() + " $" + beverage.cost());
		// 에스프레소 커피, 모카, 모카, 휘핑크림 $2.49
	}
}

0개의 댓글