ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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
    # 아메리카노 설탕 추가

    댓글