装饰器模式
本课属于 Coddy C# 学习旅程的“面向对象编程”单元。第 58 课,共 70 课。
装饰器模式是一种结构型模式,可以通过用称为装饰器的特殊对象包装对象,为对象添加新的行为。与在编译时添加行为的继承不同,装饰器可以在运行时动态添加功能,而无需修改原始类。
这种模式通过让装饰器实现与被包装对象相同的接口来工作。每个装饰器都持有对被包装对象的引用,并将调用委托给它,在调用之前或之后添加自身的行为:
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当你需要为对象添加职责,又不想创建大量子类时,装饰器模式就很合适。每个装饰器只关注一个方面,你可以按需组合它们,构建出想要的准确行为。
挑战
简单让我们使用装饰器模式构建一个披萨订购系统。你将创建一个基础披萨,然后动态地添加配料。每种配料都会包装上一层,并增加披萨的描述和价格。
你将把代码分放在 3 个文件中:
Pizza.cs:在PizzaShop命名空间中定义IPizza接口,其中包含两个方法:GetDescription()返回字符串,GetPrice()返回 decimal。然后创建一个实现此接口的PlainPizza类,它的描述应返回"Pizza",基础价格应为8.00m。ToppingDecorators.cs:创建一个实现IPizza的抽象类ToppingDecorator,并保存对被包装披萨的引用(通过构造函数传入)。它的虚方法应委托给被包装的披萨。然后创建 3 个具体装饰器:CheeseTopping:在描述后追加", Cheese",并在价格上增加1.50mPepperoniTopping:在描述后追加", Pepperoni",并在价格上增加2.00mMushroomTopping:在描述后追加", 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.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: 输出最终的描述和价格
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C# 在线编译器