데코레이터 패턴 (Decorator Pattern)
기존 클래스 코드를 바꾸지 않고도 객체에 추가 요소를 동적으로 더할 수 있다.
기능의 유연한 확장을 위해 상속대신 객체를 장식(decorator)한다.
디자인 원칙
OCP (Open-Closed Principle)
: 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.
=> 기존 코드는 건드리지 않고(Closed), 새로운 기능을 추가(Open)할 수 있어야 한다.
※ 무조건 OCP를 적용한다면 시간을 낭비할 수 있으며, 추상화를 하다 보면 필요 이상으로 복잡하고 이해하기 힘든 코드를 만들게 될 수 있다.
가장 바뀔 가능성이 높은 부분을 중점적으로 살펴보고 OCP를 적용하는 것이 좋다.
초대형 커피 전문점, 스타버즈
- 다양한 음료를 모두 포괄하는 주문 시스템 구축
방안1. 음료 종류마다 서브클래스로 구현하여 각 음료의 가격을 cost에서 리턴
문제점1. 우유, 두유, 샷추가 등 커스텀 주문이 들어오는 경우에는?
- 위와 같이 음료 종류마다 서브클래스로 구현하게 되면, 커스텀 주문을 하는 모든 경우의 클래스를 생성해야 한다.
ex) class EspressoWithSteamedMilk, class DecafWithSoy...
방안2. 각 첨가물에 해당하는 변수를 추가해보자.
public class Beverage
{
// 변수 및 메소드
public abstract double cost()
{
double totalCost = 0;
if (hasMilk())
totalCost += 500;
if (hasSoy())
totalCost += 600;
if (hasMocha())
totalCost += 550;
if (hasWhip())
totalCost += 300;
return totalCost;
}
}
public class DarkRoast : Beverage
{
public DarkRoast()
{
description = "최고의 다크 로스트 커피";
}
public override double cost()
{
return 3000 + base.cost();
}
}
문제점1. 첨가물 가격이 바뀌는 경우?
- 바뀔 때마다 기존 코드를 수정해야 한다.
문제점2. 첨가물의 종류가 많아지면?
- 종류가 추가될 때마다 새로운 변수, 메소드를 추가해야 하고 cost() 메소드도 수정해야 한다.
문제점3. 티 음료 출시?
- 모든 음료는 Beverage를 상속받으므로, 우유, 두유, 모카, 휘핑크림이 들어가지 않는 티 음료도 모든 변수와 메소드를 상속받게 된다.
방안3. 데코레이터 패턴 적용하기
1) DarkRoast 객체를 가져온다.
2) Mocha 객체로 장식한다.
3) Whip 객체로 장식한다.
4) cost() 메소드를 호출한다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPattern.DecoratorPattern
{
public abstract class Beverage
{
protected string description = "";
public virtual string GetDescription()
{
return description;
}
public abstract double Cost();
}
public class Espresso : Beverage
{
public Espresso()
{
this.description = "에스프레소";
}
public override double Cost()
{
return 1.99;
}
}
public class HouseBlend : Beverage
{
public HouseBlend()
{
this.description = "하우스 블렌드 커피";
}
public override double Cost()
{
return .89;
}
}
public class DarkRoast : Beverage
{
public DarkRoast()
{
this.description = "다크 로스트 커피";
}
public override double Cost()
{
return .90;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DesignPattern.DecoratorPattern
{
public abstract class CondimentDecorator : Beverage
{
protected Beverage beverage;
public CondimentDecorator(Beverage beverage)
{
this.beverage = beverage;
}
}
public class Mocha : CondimentDecorator
{
public Mocha(Beverage beverage) : base(beverage) { }
public override string GetDescription()
{
return beverage.GetDescription() + ", 모카";
}
public override double Cost()
{
return beverage.Cost() + .20;
}
}
public class Whip : CondimentDecorator
{
public Whip(Beverage beverage) : base(beverage) { }
public override string GetDescription()
{
return beverage.GetDescription() + ", 휘핑크림";
}
public override double Cost()
{
return beverage.Cost() + .14;
}
}
public class Soy : CondimentDecorator
{
public Soy(Beverage beverage) : base(beverage) { }
public override string GetDescription()
{
return beverage.GetDescription() + ", 두유";
}
public override double Cost()
{
return beverage.Cost() + .3;
}
}
}
'디자인패턴' 카테고리의 다른 글
[디자인패턴] Chapter 07. 어댑터 패턴과 퍼사드 패턴 (0) | 2022.11.20 |
---|---|
[디자인패턴] Chapter 05. 싱글톤 패턴 (0) | 2022.11.05 |