Decorator

프록시패턴인데 부가기능 추가 가 목적이다.

Decorator 구현체에 중복되는 기능들이 있을 수 있다. 이러한 기능들을 Decorator 라는 추상 클래스로 만들어 중복을 제거할 수 있는데, Decorator 추상 클래스 내부에서 Component 를 속성으로 가지고 있어야 한다. 이렇게 하면 추가로 클래스 다이어그램에서 어떤 것이 실제 컴포넌트 인지, 데코레이터인지 명확하게 구분할 수 있다. 이것이 바로 GOF 에서 설명하는 데코레이터 패턴이다.

The main components of the decorator pattern are:

  • Component: An interface or abstract class that defines the common behavior of the objects that will be decorated. This component can also be a concrete class in some cases.
  • Concrete Component: A concrete implementation of the component interface or class. This is the base object that will be decorated with new functionality.
  • Decorator: An abstract class or interface that extends or implements the component interface. The decorator class maintains a reference to a component object and delegates the core behavior to that object. Decorators can add or override behavior as needed.
  • Concrete Decorator: A concrete implementation of the decorator class that defines the specific additional functionality or behavior to be added to the component.

클래스 기반으로 데코레이터 패턴을 구현할 수 있다.

@Slf4j
public class ConcreteLogic {

    public String call() {
        log.info("ConcreteLogic 실행");
        return "data";
    }
}
@Slf4j
public class TimeProxy extends ConcreteLogic {

    private ConcreteLogic concreteLogic;

    public TimeProxy(ConcreteLogic concreteLogic) {
        this.concreteLogic = concreteLogic;
    }

    @Override
    public String call() {
        log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();

        String result = concreteLogic.call();

        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeDecorator 종료 resultTime={}ms", resultTime);
        return result;
    }
}
public class ConcreteClient {

    private ConcreteLogic concreteLogic;

    public ConcreteClient(ConcreteLogic concreteLogic) {
        this.concreteLogic = concreteLogic;
    }

    public void execute() {
        concreteLogic.call();
    }
}
@DisplayName("구체 클래스 기반 프록시 테스트")
@Test
void concreteProxyTest() throws Exception {
    ConcreteLogic concreteLogic = new ConcreteLogic();
    TimeProxy timeProxy = new TimeProxy(concreteLogic);
    ConcreteClient client = new ConcreteClient(timeProxy);
    client.execute();
}

Beverage pricing system

// Component
interface Beverage {
    fun getDescription(): String
    fun cost(): Double
}

// Concrete Component
class Coffee : Beverage {
    override fun getDescription(): String = "Coffee"

    override fun cost(): Double = 1.50
}

// Another Concrete Component
class Tea : Beverage {
    override fun getDescription(): String = "Tea"

    override fun cost(): Double = 1.00
}

// Decorator
abstract class BeverageDecorator(private val beverage: Beverage) : Beverage {
    override fun getDescription(): String = beverage.getDescription()

    override fun cost(): Double = beverage.cost()
}

// Concrete Decorator
class Milk(beverage: Beverage) : BeverageDecorator(beverage) {
    override fun getDescription(): String = super.getDescription() + ", Milk"

    override fun cost(): Double = super.cost() + 0.25
}

// Another Concrete Decorator
class Sugar(beverage: Beverage) : BeverageDecorator(beverage) {
    override fun getDescription(): String = super.getDescription() + ", Sugar"

    override fun cost(): Double = super.cost() + 0.15
}

// Usage
fun main() {
    val coffeeWithMilkAndSugar = Sugar(Milk(Coffee()))
    println("${coffeeWithMilkAndSugar.getDescription()} costs ${coffeeWithMilkAndSugar.cost()}")

    val teaWithMilk = Milk(Tea())
    println("${teaWithMilk.getDescription()} costs ${teaWithMilk.cost()}")
}

Outputs:

Coffee, Milk, Sugar costs 1.9
Tea, Milk costs 1.25