Menu
Coddy logo textTech

데코레이터 패턴

Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 70개 중 58번째.

데코레이터 패턴(Decorator pattern)은 객체들을 데코레이터라고 불리는 특별한 객체들로 감싸서 새로운 행동들을 해당 객체들에 추가할 수 있게 해주는 구조적 패턴입니다. 컴파일 타임에 행동을 추가하는 상속과 달리, 데코레이터는 기존 클래스를 수정하지 않고도 런타임에 동적으로 기능을 추가합니다.

이 패턴은 데코레이터가 래핑하는 객체와 동일한 인터페이스를 구현하도록 함으로써 작동합니다. 각 데코레이터는 래핑된 객체에 대한 참조를 유지하고 호출을 해당 객체로 위임하며, 그 전후에 자체적인 동작을 추가합니다:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

데코레이터의 강력함은 중첩할 수 있는 능력에 있습니다. 데코레이터가 적용된 객체를 또 다른 데코레이터로 감쌀 수 있습니다:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // 우유 두 번 추가!

Console.WriteLine(coffee.GetDescription());  // 커피, 우유, 우유
Console.WriteLine(coffee.GetCost());         // 3.00

데코레이터 패턴은 서브클래스를 폭발적으로 늘리지 않고도 객체에 책임을 추가해야 할 때 이상적입니다. 각 데코레이터는 단일 관심사에 집중하며, 필요에 따라 이들을 조합하여 원하는 정확한 동작을 구성할 수 있습니다.

challenge icon

챌린지

쉬움

데코레이터(Decorator) 패턴을 사용하여 피자 주문 시스템을 구축해 보겠습니다. 기본 피자를 만든 다음 토핑을 동적으로 추가할 것입니다. 각 토핑은 이전 레이어를 감싸며 설명과 가격을 모두 추가합니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • Pizza.cs: PizzaShop 네임스페이스 안에 문자열을 반환하는 GetDescription()과 decimal을 반환하는 GetPrice()라는 두 개의 메서드를 가진 IPizza 인터페이스를 정의합니다. 그런 다음 이 인터페이스를 구현하는 PlainPizza 클래스를 만듭니다. 이 클래스는 설명으로 "Pizza"를, 기본 가격으로 8.00m을 반환해야 합니다.
  • ToppingDecorators.cs: IPizza를 구현하고 (생성자를 통해 전달된) 감싸진 피자에 대한 참조를 보유하는 추상 ToppingDecorator 클래스를 만듭니다. 이 클래스의 가상 메서드들은 감싸진 피자에게 작업을 위임해야 합니다. 그런 다음 세 개의 구체적인 데코레이터를 만듭니다:
    • CheeseTopping - 설명에 ", Cheese"를 추가하고 가격에 1.50m을 더합니다.
    • PepperoniTopping - 설명에 ", Pepperoni"를 추가하고 가격에 2.00m을 더합니다.
    • MushroomTopping - 설명에 ", Mushrooms"를 추가하고 가격에 1.25m을 더합니다.
  • Program.cs: 기본 피자로 시작하여 입력에 따라 토핑으로 감싸며 모든 것을 하나로 합칩니다. 각 토핑 데코레이터는 이전 피자를 감싸며 최종 주문을 레이어별로 쌓아 올립니다.

하나의 입력을 받게 됩니다:

  • 추가할 토핑의 쉼표로 구분된 리스트 (예: cheese,pepperoni,cheese)

PlainPizza로 시작하여, 나타나는 순서대로 각 토핑에 맞는 데코레이터로 감싸세요. 유효한 토핑은 cheese, pepperoni, mushrooms입니다. 모든 토핑을 적용한 후, 첫 번째 줄에는 설명을, 다음 줄에는 총 가격을 출력하세요.

예를 들어, 입력이 cheese,pepperoni,mushrooms라면 출력은 다음과 같아야 합니다:

Pizza, Cheese, Pepperoni, Mushrooms
12.75

동일한 토핑을 여러 번 추가할 수 있다는 점에 유의하세요! 입력이 cheese,cheese라면 다음과 같은 결과를 얻게 됩니다:

Pizza, Cheese, Cheese
11

이것은 데코레이터 패턴의 유연성을 보여줍니다. 각 데코레이터는 그것이 기본 피자이든 이미 데코레이션된 피자이든 상관없이 전달받은 피자를 감싸며, 동작을 동적으로 쌓아 올릴 수 있게 해줍니다!

치트 시트

데코레이터 패턴(Decorator pattern)은 객체를 특별한 데코레이터 객체로 감싸서 새로운 행동을 추가하는 구조적 패턴입니다. 원본 클래스를 수정하지 않고도 런타임에 동적으로 기능을 추가할 수 있습니다.

데코레이터는 감싸고 있는 객체와 동일한 인터페이스를 구현하며, 감싸진 객체에 대한 참조를 유지하고, 자체적인 행동을 추가하면서 호출을 해당 객체로 전달합니다:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

데코레이터는 데코레이션된 객체를 다른 데코레이터로 다시 감싸는 방식으로 중첩해서 사용할 수 있습니다:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // 우유 두 번 추가!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

수많은 서브클래스를 생성하지 않고 객체에 책임을 추가해야 할 때 데코레이터 패턴을 사용하세요. 각 데코레이터는 단일 관심사에 집중하며 필요에 따라 조합될 수 있습니다.

직접 해보기

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // 쉼표로 구분된 토핑 목록을 읽습니다
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: PlainPizza로 시작하세요
        
        // TODO: 각 토핑을 순회하며 피자를 감싸세요
        // 토핑 이름에 따라 적절한 데코레이터를 사용하세요:
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: 최종 설명과 가격을 출력하세요
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨