1、简介
简要说明就是动态地给一个对象添加一些额外的职责。适用于需要扩展一个类的功能,或给一个类添加多个变化的情况。
装饰器,顾名思义就是在原有基础上添加一些功能。
装饰器模式中各个角色有:
抽象构件(Component)角色:定义一个对象接口,可以给这些对象动态地添加一些职责。
具体构件(ConcreteComponent)角色:定义了一个具体的对象,也可以给这个对象添加一些职责。
抽象装饰类(Decorator)角色:持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口。
具体装饰类(ConcreteDecorator)角色:负责给构件对象“贴上”一些附加的职责。
2、适用场景
扩展一个类的功能:原有类无法修改或者修改困难的情况下,对类进行多次扩展或功能性比较相互独立,有效防止多次扩展的情况下子类的膨胀。
动态增加或撤销功能:需要动态地给对象添加或移除功能时。
多个功能组合:需要实现多个功能的不同组合时。
3、举例说明
现在有一个手机,其仅有打电话的功能。现在需要给它加一个看视频和放音乐的功能。
传统做法如下:
手机类:
/// <summary>
/// 手机类
/// </summary>
public class Phone
{
public void Call()
{
Console.WriteLine("打电话");
}
}
增加功能:
public class FYY:Phone
{
public void Install()
{
Console.WriteLine("放音乐!");
}
}
public class FSP : Phone
{
public void Install()
{
Console.WriteLine("放视频!");
}
}
使用:
private void WTBtn_Click(object sender, EventArgs e)
{
Phone phone0 = new Phone();//旧版手机
phone0.Call();//仅有打电话的功能
Console.WriteLine("\n");
FYY phone = new FYY();//新版手机1
phone.Call();//打电话,手机本来的功能
phone.Install();//放音乐,给手机拓展的东西
Console.WriteLine("\n");
FSP phone1=new FSP();//新版手机2
phone1.Call();//打电话,手机本来的功能
phone1.Install();//放视频,给手机拓展的东西
}
可以看出,每当需要增加一个功能,就需要增加一个子类。这种简单的单一功能问题还不大,当你既要放音乐又要放视频的功能的话,那你要不就是再实现一个之类去包含两种功能,要不就多重继承让放音乐的类继承放视频的类。这样实例化放音乐的类就能达到使用两种功能。当需要的功能越多,继承就越深,或者有重复方法的类就越多。但这显然不科学。 装饰器模式就能很好解决此类问题。
装饰器模式
抽象构件(Component)角色
/// <summary>
/// 抽象构件角色(Component)
/// </summary>
public abstract class IPhone
{
public abstract void Call();
}
具体构件(ConcreteComponent)角色
/// <summary>
/// 具体构件角色(Concrete Component)
/// </summary>
public class Phone : IPhone
{
public override void Call()
{
Console.WriteLine("打电话");
}
}
抽象装饰类(Decorator)角色
/// <summary>
/// 抽象装饰类(Decorator)角色
/// </summary>
public abstract class PhoneDecorator : IPhone
{
protected IPhone Phone { get; }
public PhoneDecorator(IPhone phone)
{
Phone = phone;
}
/// <summary>
/// 可以定义新增的功能方法,也可以不定义,直接给Call方法添加装饰
/// </summary>
public abstract void Install();
}
具体装饰类(ConcreteDecorator)角色
/// <summary>
/// 具体装饰类(ConcreteDecorator)角色
/// </summary>
// 放音乐功能装饰器
public class YYDecorator : PhoneDecorator
{
public YYDecorator(IPhone phone) : base(phone)
{
}
public override void Call()
{
base.Phone.Call();
RunYY();//也可以给打电话这件事添加放音乐的功能
}
public override void Install()
{
Console.WriteLine("安装音乐!");
}
public void RunYY()
{
Console.WriteLine("语音通话!");
}
}
/// <summary>
/// 具体装饰类(ConcreteDecorator)角色
/// </summary>
// 放音乐功能装饰器
public class SPDecorator : PhoneDecorator
{
public SPDecorator(IPhone phone) : base(phone)
{
}
public override void Call()
{
base.Phone.Call();
RunSP();//也可以给打电话这件事添加视频通话的功能
}
public override void Install()
{
Console.WriteLine("安装视频!");
}
public void RunSP()
{
Console.WriteLine("视频通话!");
}
}
使用:
private void WTBtn_Click(object sender, EventArgs e)
{
IPhone phone = new Phone();//初始手机
phone.Call();//初始手机打电话
Console.WriteLine("");
PhoneDecorator phone1 = new YYDecorator(phone);//语音通话手机
phone1.Install();//安装语音通话功能
phone1.Call();//安装语音通话功能的手机打电话
Console.WriteLine("");
PhoneDecorator phone2 = new SPDecorator(phone);//视频通话手机
phone2.Install();//安装视频通话功能
phone2.Call();//安装视频通话功能的手机打电话
Console.WriteLine("");
PhoneDecorator phone3 = new SPDecorator(phone);//视频的手机
phone3.Install();//安装视频功能
phone3 = new YYDecorator(phone3);//语音的手机
phone3.Install();//安装音乐功能
phone3.Call();//双支持的手机再打电话
}
注意:Call()方法和Install()方法都是实现了装饰器,一个是给原本的功能前后添加功能段,一个是独立添加其他的功能。
可以看出使用装饰类如果需要实现组合配置,仅需要对像重新实例化即可,无需创建新的对象,和使用多重继承了。 若需要增加其他功能仅需要增加具体装饰类(ConcreteDecorator)角色即可。
最后:
优点:
1、灵活性:装饰器模式可以以动态的方式在运行时给对象增加额外的职责,而不需要在编译时决定添加哪些功能。通过使用装饰器模式,可以在不改变原始对象结构的情况下,根据需要灵活地扩展对象的行为。
2、可插拔:通过使用装饰器模式,可以将功能分解成一系列的装饰器类,使得代码更加模块化和易于维护。可以在运行时动态地组合和替换装饰器对象,从而改变对象的行为。
3、可扩展性:装饰器模式可以避免继承带来的类膨胀问题,因为你可以通过组合装饰器对象来扩展对象的行为,而不是通过继承来添加新的功能。
4、符合开闭原则:装饰器模式完全遵守开闭原则,即对扩展开放,对修改封闭。通过使用装饰器模式,可以方便地添加新的装饰器类来扩展对象的行为,而不需要修改原始对象的代码。
缺点:
1、代码量增加:装饰器模式需要创建很多小类,即使只添加一个功能,也要额外创建一个类,这会使得程序更复杂。
2、增加代码复杂度:使用装饰器模式不但需要实例化组件,还要把组件包装到装饰者中,这会增加代码的复杂度。
3、设计难度高:装饰器模式需要对系统进行全面理解,设计出结构良好的装饰器类和被装饰类,才能够达到预期的效果。
4、性能问题:由于装饰器模式需要在运行时动态地创建对象和调用方法,这可能会导致性能上的问题。
注意:过度使用装饰器模式可能会导致程序变得复杂,增加系统中类的数量,并可能产生大量小粒度对象,使得代码变得难以维护。因此,在使用装饰器模式时需要谨慎考虑设计是否合适。