🚀介绍
在装饰者模式中,装饰者类通常对原始类的功能进行增强或减弱。这种模式是在不必改变原始类的情况下,动态地扩展一个对象的功能。这种类型的设计模式属于结构型模式,因为这种模式涉及到两个类型之间的关系,这两个类型是组合在一起的,这种组合关系通常是通过继承来实现的。
装饰者模式的主要优点是可以在不修改原始类的情况下,通过使用单个类来包装其对象,动态地扩展一个对象的功能。其主要缺点是装饰者模式会导致设计中出现很多小类,如果过度使用,会使程序变得复杂。
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
🚀案例
我们使用一个案例来对快餐店的点餐功能进行改进,快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、培根这些配菜,加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦,这时候我们就可以使用装饰者模式,在不改变原始类的情况下,动态扩展对象功能。
🐤首先,创建一个最基本的主食抽象类,定义了价格和类型两个属性,以及获取费用和获取类型两个抽象方法
public abstract class FastFood
{
/// <summary>
/// 价格
/// </summary>
public float _price { get; set; }
/// <summary>
/// 类型
/// </summary>
public string _desc { get; set; }
public FastFood()
{
}
public FastFood(float price, string desc)
{
_price = price;
_desc = desc;
}
/// <summary>
/// 获取费用
/// </summary>
/// <returns></returns>
public abstract float GetCost();
/// <summary>
/// 获取类型
/// </summary>
/// <returns></returns>
public abstract string GetDesc();
}
🐤然后,对于上面的主食抽象类分别增加两个实现类,炒饭和炒面,在这里通过基类的含有(float price, string desc)参数的构造函数分别对价格和类型赋值,如果在此处直接调用了GetDesc,那么此时的炒饭和炒粉是什么都还没加的,因此我们在重写GetDesc加上了"啥都不加"字符
/// <summary>
/// 炒饭
/// </summary>
public class FriedRice : FastFood
{
public FriedRice() : base(10, "炒饭")
{
}
public override float GetCost()
{
return _price;
}
public override string GetDesc()
{
return _desc + " 啥都不加";
}
}
/// <summary>
/// 炒面
/// </summary>
public class FriedNoodles : FastFood
{
public FriedNoodles() : base(12, "炒面")
{
}
public override float GetCost()
{
return _price;
}
public override string GetDesc()
{
return _desc + " 啥都不加";
}
}
🐤创建一个配料抽象类继承于主食抽象类,并且定义了一个FastFood(主食)类型的属性_fastFood
/// <summary>
/// 配料类
/// </summary>
public abstract class Garnish : FastFood
{
public FastFood _fastFood { get; set; }
}
🐤对配料类做两个实现,鸡蛋和培根,通过这两个对象,我们可以动态地给一个FastFood对象添加鸡蛋或培根,并计算出新的价格和描述,这就是装饰者模式的核心思想。
在方法中我们再次重写了获取类型和价格的方法。
GetCost()方法是用来计算总价的,即鸡蛋或培根的价格加上被装饰对象的价格。
GetDesc()方法是用来获取描述的,即鸡蛋或培根的描述加上被装饰对象的描述。
/// <summary>
/// 添加鸡蛋
/// </summary>
public class Egg : Garnish
{
public Egg(FastFood fastFood)
{
_fastFood = fastFood;
_desc = "鸡蛋";
_price = 3;
}
public override float GetCost()
{
return _price + _fastFood._price;
}
public override String GetDesc()
{
return _desc + _fastFood._desc;
}
}
/// <summary>
/// 添加培根
/// </summary>
public class Bacon : Garnish
{
public Bacon(FastFood fastFood)
{
_fastFood = fastFood;
_price = 5;
_desc = "培根";
}
public override float GetCost()
{
return _price + _fastFood._price;
}
public override String GetDesc()
{
return _desc + _fastFood._desc;
}
}
🐤测试类
class MyClass
{
public static void Main(string[] args)
{
//点一份炒饭
FastFood riceFood = new FriedRice();
//花费的价格
Console.WriteLine(riceFood.GetDesc() + " " + riceFood.GetCost() + "元");
//点一份炒面
FastFood noodleFood = new FriedNoodles();
//花费的价格
Console.WriteLine(noodleFood.GetDesc() + " " + noodleFood.GetCost() + "元");
//点一份加鸡蛋的炒饭
FastFood food1 = new FriedRice();
food1 = new Egg(food1);
//花费的价格
Console.WriteLine(food1.GetDesc() + " " + food1.GetCost() + "元");
//点一份加培根的炒面
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花费的价格
Console.WriteLine(food2.GetDesc() + " " + food2.GetCost() + "元");
}
}
🐳运行结果
🚀总结
- 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象 来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
- 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
使用场景
当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。