总目录
前言
装饰器模式的主要作用就是扩展一个类的功能,或给一个类添加多个变化的情况。学习面向对象的都知道,如果想单纯的给某个类增加一些功能,可以直接继承该类生成一个子类就可以。应对一些简单的业务场景继承也就够了,但是面对一些复杂的业务场景,仅靠继承是不够的。那么我们看看装饰器模式是如果以一个更为灵活的方式扩展一个对象的功能的。
1 基础介绍
- 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
- 适用于需要扩展一个类的功能,或给一个类添加多个变化的情况。
- 在装饰模式中的角色:
- 抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件角色(Concrete Component):定义一个将要接收附加责任的类。
- 装饰角色(Decorator):持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
- 具体装饰角色(Concrete Decorator):负责给构件对象添加上附加的责任。
2 使用场景
当需要扩展一个类的功能或给一个类增加附加责任,并且扩展功能可能存在多个且需要可动态组合配置的情况下,使用装饰器模式就是最好的解决办法
注:如果类的扩展比较简单,并且不会多次进行扩展的情况下直接使用类的继承生成子类的方式更为方便快捷。
3 实现方式
看了上面这些描述,没有案例我们是无法理解装饰器模式的精髓。那么我们现在通过一个手机贴膜,装手机壳的案例来理解一下。
1. 传统模式
(1) 第一个需求,张三有一个手机,现在想给贴膜,代码实现如下:
csharp
public class Phone
{
public virtual void Show()
{
Console.WriteLine("手机");
}
}
//贴膜手机 继承自 手机类
public class MoPhone : Phone
{
//贴膜的方法
public void TieMo()
{
Console.WriteLine("给手机贴膜了!");
}
public override void Show()
{
base.Show();
TieMo();
}
}
客户端调用:
csharp
static void Main(string[] args)
{
Phone moPhone = new MoPhone();
moPhone.Show();
Console.ReadKey();
}
(2) 现在需求改了,手机不贴膜了,要装手机壳,于是我们改代码:
csharp
//装手机壳的手机 继承自 手机类
public class KePhone : Phone
{
//装手机壳的方法
public void ZhuangKe()
{
Console.WriteLine("给手机装手机壳了!");
}
public override void Show()
{
base.Show();
ZhuangKe();
}
}
客户端调用:
csharp
static void Main(string[] args)
{
Phone phone = new KePhone();
phone.Show();
Console.ReadKey();
}
(3) 现在需求又改了,手机贴膜 + 装手机壳,于是我们改代码:
csharp
public class Phone
{
public virtual void Show()
{
Console.WriteLine("手机");
}
}
//贴膜手机 继承自 手机类
public class MoPhone : Phone
{
//贴膜的方法
public void TieMo()
{
Console.WriteLine("给手机贴膜了!");
}
public override void Show()
{
base.Show();
TieMo();
}
}
//现在又改变注意了,既想给手机贴膜也想给手机装手机壳
//直接继承已有的贴膜手机类来实现会比较省事
public class KeAndMoPhone : MoPhone
{
//装手机壳的方法
public void ZhuangKe()
{
Console.WriteLine("给手机装手机壳了!");
}
public override void Show()
{
base.Show();
ZhuangKe();
}
}
客户端调用:
csharp
static void Main(string[] args)
{
Phone phone = new KeAndMoPhone();
phone.Show();
Console.ReadKey();
}
上面的实例中,如果单独贴膜或者单独安装保护壳则直接继承手机类即可。
但如果想要即贴膜又要安装保护壳,各自继承手机类的方式就行不通了,只能在贴膜类或者保护壳类的基础上进行扩展。如果还有添加手机挂饰,那就还需要再一层继承关系,这样就会导致 "子类爆炸"问题,为了解决这个问题就用到了装饰器,下面看看使用装饰器是怎么给手机添加新功能的。
2. 装饰器模式
1 首先定义手机抽象类 和 手机实现类
csharp
public abstract class AbstractPhone
{
public abstract void Show();
}
public class XiaoMiPhone : AbstractPhone
{
public override void Show()
{
Console.WriteLine("小米手机");
}
}
2 再定义一个装饰的抽象类
csharp
//装饰抽象类,是装饰模式的核心
public abstract class Decorator : AbstractPhone
{
//保持对手机对象的引用
protected AbstractPhone abstractPhone;
public Decorator(AbstractPhone phone)
{
abstractPhone = phone;
}
public override void Show()
{
//这行代码比较有意思,是实现装饰模式的巧思
abstractPhone?.Show();
}
}
3 定义装饰抽象类的实现:贴膜装饰,装手机壳装饰
csharp
// 贴膜装饰类,主要实现给手机贴膜的扩展功能
public class MoPhone : Decorator
{
public MoPhone(AbstractPhone phone) : base(phone)
{
}
public override void Show()
{
base.Show();
TieMo();
}
//扩展的功能:贴膜
public void TieMo()
{
Console.WriteLine("给手机贴膜了!");
}
}
csharp
// 手机壳装饰类,主要实现给手机装手机壳的扩展功能
public class KePhone : Decorator
{
public KePhone(AbstractPhone phone) : base(phone)
{
}
public override void Show()
{
base.Show();
ZhuangKe();
}
//扩展的功能:装手机壳
public void ZhuangKe()
{
Console.WriteLine("给手机装手机壳了!");
}
}
客户端调用:
- 只给手机贴膜
csharp
static void Main(string[] args)
{
AbstractPhone phone1 = new XiaoMiPhone();
Decorator decorator1 = new MoPhone(phone1);
decorator1.Show();
Console.ReadKey();
}
- 只给手机装手机壳
csharp
static void Main(string[] args)
{
AbstractPhone phone2 = new XiaoMiPhone();
Decorator decorator2 = new KePhone(phone2);
decorator2.Show();
Console.ReadKey();
}
- 给手机贴膜+装手机壳
csharp
static void Main(string[] args)
{
AbstractPhone phone3 = new XiaoMiPhone();
Decorator decorator3 = new MoPhone(phone3);
decorator3 = new KePhone(decorator3);
decorator3.Show();
Console.ReadKey();
}
4 需求变更:现在想给手机 贴膜 + 玩偶吊坠
我们只需新增一个 玩偶吊坠 类 继承自 装饰抽象类,然后定义一个玩偶吊坠的装饰方法
csharp
// 玩偶吊坠装饰类,主要实现给手机装玩偶吊坠的扩展功能
public class DiaoZhuiPhone : Decorator
{
public DiaoZhuiPhone(AbstractPhone phone) : base(phone)
{
}
public override void Show()
{
base.Show();
ZhuangDiaoZhui();
}
//扩展功能:给手机装玩偶吊坠
public void ZhuangDiaoZhui()
{
Console.WriteLine("给手机安装玩偶吊坠了!");
}
}
客户端调用:
csharp
static void Main(string[] args)
{
//给手机贴膜+玩偶吊坠
AbstractPhone phone = new XiaoMiPhone();
Decorator decorator = new MoPhone(phone);
decorator = new DiaoZhuiPhone(decorator);
decorator.Show();
Console.ReadKey();
}
我们发现当我们想要给手机加新的装饰,只需简单的新增对应的装饰类,在装饰类定义一个扩展的装饰方法(新功能)即可。而且还可以对装饰类进行不同组合,这使得我们的代码非常的灵活。
4 优缺点分析
- 优点:
- 相较于继承,装饰器模式可以更为灵活的扩展新功能,并且避免了单独使用继承带来的 "多子类衍生问题"。
- 很好地符合面向对象设计原则中 "优先使用对象组合而非继承"和"开放-封闭"原则。装饰者模式有很好地可扩展性。
- 缺点:装饰者模式会导致设计中出现许多小对象,如果过度使用,会让程序变的更复杂。并且更多的对象会是的差错变得困难,特别是这些对象看上去都很像。
结语
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
C#设计模式之八装饰模式(Decorator Pattern)【结构型】
c#中装饰器模式详解
C#设计模式(9)------装饰者模式(Decorator Pattern)