데코레이터 패턴
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데코레이터 패턴은 서브클래스를 폭발적으로 늘리지 않고도 객체에 책임을 추가해야 할 때 이상적입니다. 각 데코레이터는 단일 관심사에 집중하며, 필요에 따라 이들을 조합하여 원하는 정확한 동작을 구성할 수 있습니다.
챌린지
쉬움데코레이터(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: 최종 설명과 가격을 출력하세요
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.