본문 바로가기
카테고리 없음

데코레이터 패턴(Decorator pattern)에 대하여

by nono22 2024. 1. 18.

데코레이터 패턴(Decorator pattern)

데코레이터 패턴은 객체의 기능을 동적으로 확장하기 위해 사용되는 디자인 패턴 중 하나입니다. 데코레이터 패턴은 기본 객체에 대해 런타임에 새로운 기능을 추가함으로써 객체의 행동을 유연하게 확장할 수 있습니다.

기본 개념

데코레이터 패턴은 객체를 감싸는 래퍼(Wrapper) 클래스를 사용하여 객체의 기능을 재정의합니다. 이렇게 되면 기본 객체를 참조하는 변수에 여러 개의 데코레이터 객체를 사용할 수 있으며, 각 데코레이터 객체는 자신의 기능을 추가할 수 있습니다.

데코레이터 패턴은 객체 간의 상속 관계를 통해 기능을 확장하는 것과 달리, 런타임에서 객체의 행동을 동적으로 확장하기 때문에 더욱 유연하고 확장성이 높습니다. 데코레이터 패턴을 사용하면 객체에 필요한 동작을 원하는대로 추가하거나 제거할 수 있습니다.

구성 요소

데코레이터 패턴은 기본 객체, 컴포넌트 인터페이스, 데코레이터 클래스로 구성됩니다.

  1. 기본 객체: 장식할 기본 객체를 나타냅니다. 데코레이터 패턴은 기본 객체에 대한 참조를 갖고 있어야 합니다.

  2. 컴포넌트 인터페이스: 데코레이터와 기본 객체가 구현해야 하는 메서드를 정의하는 인터페이스입니다. 이를 통해 데코레이터는 자신만의 기능을 확장하고 기본 객체의 메서드를 호출할 수 있습니다.

  3. 데코레이터 클래스: 컴포넌트 인터페이스를 구현하며, 기본 객체를 참조하고 자신의 기능을 추가하는 클래스입니다. 데코레이터 클래스는 기본 객체와 같은 인터페이스를 구현하여 클라이언트가 원하는 수만큼의 데코레이터를 연결할 수 있도록 합니다.

동작 방식

  1. 기본 객체 생성: 기본 객체를 생성합니다.

  2. 데코레이터 생성: 데코레이터 객체를 생성하며, 기본 객체를 참조합니다.

  3. 기본 객체 참조: 데코레이터 객체는 자신이 참조하는 기본 객체를 호출하고, 필요한 경우에는 추가적인 기능을 수행합니다.

  4. 데코레이터 체인 구성: 클라이언트는 데코레이터 객체의 체인을 구성하여 여러 개의 데코레이터를 적용할 수 있습니다. 이때 첫 번째 데코레이터는 기본 객체를 참조하고, 이후의 데코레이터는 이전 데코레이터를 참조합니다.

  5. 클라이언트 호출: 클라이언트는 데코레이터 체인에 있는 첫 번째 데코레이터를 호출하여 결과를 얻습니다. 이때 체인을 따라가며 각 데코레이터에서 필요한 작업을 수행한 뒤, 마지막으로 기본 객체를 호출하여 최종 결과를 얻습니다.

장점

  1. 객체 간의 상속을 사용하지 않고도 기능을 유연하게 확장할 수 있습니다.

  2. 런타임에 동적으로 기능을 추가하거나 제거할 수 있습니다.

  3. 유연하고 확장성이 높은 구조를 갖게 됩니다.

예시

가령, 커피 주문 시스템을 구현한다고 가정해봅시다. 커피에는 기본적인 특징(온도, 시럽 추가 여부, 휘핑 크림 추가 여부 등)이 있지만, 고객은 이 외에도 개인적인 요구사항을 추가할 수 있어야 합니다. 이때 데코레이터 패턴을 사용하면 기본 커피 객체에 원하는 만큼의 데코레이터를 추가하여 고객의 요구에 맞는 커피를 제공할 수 있습니다.

public interface Coffee {
    String getDescription();
    double getCost();
}

public class BasicCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Basic Coffee";
    }

    @Override
    public double getCost() {
        return 2.0;
    }
}

public abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double getCost() {
        return coffee.getCost();
    }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " with sugar";
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.5;
    }
}

public class WhipCreamDecorator extends CoffeeDecorator {
    public WhipCreamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " with whipped cream";
    }

    @Override
    public double getCost() {
        return super.getCost() + 1.0;
    }
}

public class Client {
    public static void main(String[] args) {
        Coffee basicCoffee = new BasicCoffee();
        Coffee sugarCoffee = new SugarDecorator(basicCoffee);
        Coffee whippedCreamCoffee = new WhipCreamDecorator(sugarCoffee);

        System.out.println(whippedCreamCoffee.getDescription());
        System.out.println(whippedCreamCoffee.getCost());    
    }
}

위 예시에서 기본 커피 객체인 BasicCoffee를 생성한 뒤, SugarDecoratorWhipCreamDecorator를 차례대로 추가하여 최종 커피를 생성했습니다. 이렇게 함으로써 고객은 원하는 만큼의 데코레이터를 이용해 본인에게 맞는 커피를 주문할 수 있게 되었습니다.

결론

데코레이터 패턴은 객체의 기능을 동적으로 확장하기 위한 강력한 디자인 패턴입니다. 객체 간의 상속을 이용하지 않고도 유연하고 확장성이 높은 구조를 갖출 수 있으며, 런타임에 동적으로 기능을 추가하거나 제거할 수 있습니다. 이를 통해 객체 지향 프로그래밍의 핵심 원칙 중 하나인 개방-폐쇄 원칙(OCP)을 준수할 수 있습니다.

댓글