-
[Design Pattern] 데코레이터 패턴(Decorator Pattern)공부/디자인 패턴 2021. 5. 31. 21:37
데코레이터 패턴이란 어떤 객체에 상황과 용도에 따라 새로운 책임을 추가하는 형식입니다. 객체에 기능을 동적으로 추가하여 서브 클래스를 생성하는 것보다 유연하게 기능을 확장할 수 있습니다.
클래스 다이어그램
Component: ConcreateComponent와 Decorator를 위한 인터페이스.
ConcreteComponent: 기능 추가를 받을 기본 객체
Decorator: 기능 추가를 할 객체를 위한 추상 클래스
ConcreteDecorator: Decorator를 상속받아 구현할 객체. ConcreteComponent에 추가하기 위해 생성
예제
카페를 차려서 커피를 팔게 되어 아래와 같이 메뉴를 구성했습니다.
public interface Beverage { int getCost(); String getTitle(); } public class Americano implements Beverage { @Override public int getCost() { return 3000; } @Override public String getTitle() { return "아메리카노"; } } public class CaffeLatte implements Beverage { @Override public int getCost() { return 4000; } @Override public String getTitle() { return "카페라떼"; } } public class caffeMocha implements Beverage { @Override public int getCost() { return 4500; } @Override public String getTitle() { return "카페모카"; } }
이렇게 팔고 있는데 갑자기 샷 추가, 휘핑크림 추가, 헤이즐넛 추가 등 부가 옵션이 들어오고 커피 뿐만 아니라 차와 탄산수와 같은 다른 음료도 팔게 된다면 위 구조에서는 인터페이스의 메서드, 서브 클래스가 계속 추가 수정이 되어야 합니다.
이러한 문제를 데코레이터 패턴을 사용하면 아래와 같이 해결할 수 있습니다.
// Component public interface Beverage { int getCost(); String getTitle(); } // ConcreteComponent public class Americano implements Beverage { @Override public int getCost() { return 3000; } @Override public String getTitle() { return "아메리카노"; } } // ConcreteComponent public class HerbalTea implements Beverage { @Override public int getCost() { return 4000; } @Override public String getTitle() { return "허브티"; } } // Decorator public abstract class CondimentDecorator implements Beverage { private final Beverage beverage; public CondimentDecorator(Beverage beverage) { this.beverage = beverage; } @Override public int getCost() { return beverage.getCost(); } @Override public String getTitle() { return beverage.getTitle(); } } // ConcreteDecorator public class Sugar extends CondimentDecorator { public Sugar(Beverage beverage) { super(beverage); } @Override public int getCost() { return super.getCost() + 200; } @Override public String getTitle() { return super.getTitle() + " 설탕 추가"; } } // ConcreteDecorator public class Shot extends CondimentDecorator { public Shot(Beverage beverage) { super(beverage); } @Override public int getCost() { return super.getCost() + 500; } @Override public String getTitle() { return super.getTitle() + " 샷 추가"; } } Beverage americano = new Americano(); Beverage americanoOneShot = new Shot(americano); System.out.println(americanoOneShot.getCost()); System.out.println(americanoOneShot.getTitle()); // 3500 // 아메리카노 샷 추가 Beverage herbalTea = new HerbalTea(); Beverage herbalTeaOneSugar = new Sugar(herbalTea); System.out.println(herbalTeaOneSugar.getCost()); System.out.println(herbalTeaOneSugar.getTitle()); // 4200 // 허브티 설탕 추가
파이썬
파이썬에서는 interface가 없으므로 추상 클래스로 구현할 수 있습니다.
from abc import ABCMeta, abstractmethod class Beverage(metaclass=ABCMeta): """ Component """ @abstractmethod def get_cost(self) -> int: pass @abstractmethod def get_description(self) -> str: pass class Americano(Beverage): """ Concrete Component """ def get_cost(self) -> int: return 3000 def get_description(self) -> str: return '아메리카노' class HerbalTea(Beverage): """ Concrete Component """ def get_cost(self) -> int: return 4000 def get_description(self) -> str: return '허브티' class CondimentDecorator(Beverage): """ Decorator """ def __init__(self, beverage: Beverage): self.__beverage = beverage @abstractmethod def get_cost(self) -> int: return self.__beverage.get_cost() @abstractmethod def get_description(self) -> str: return self.__beverage.get_description() class Sugar(CondimentDecorator): """ Concrete Decorator """ def __init__(self, beverage: Beverage): super().__init__(beverage) def get_cost(self) -> int: return super().get_cost() + 200 def get_description(self) -> str: return super().get_description() + ' 설탕 추가' class Shot(CondimentDecorator): """ Concrete Decorator """ def __init__(self, beverage: Beverage): super().__init__(beverage) def get_cost(self) -> int: return super().get_cost() + 500 def get_description(self) -> str: return super().get_description() + ' 샷 추가' americano = Americano() americano_one_sugar = Sugar(americano) print(americano_one_sugar.get_cost()) print(americano_one_sugar.get_description()) # 3200 # 아메리카노 설탕 추가