C# 结构型设计模式----装饰器模式

embedded/2024/10/31 3:07:40/

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();//新版手机1phone.Call();//打电话,手机本来的功能phone.Install();//放音乐,给手机拓展的东西Console.WriteLine("\n");FSP phone1=new FSP();//新版手机2phone1.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、性能问题:由于装饰器模式需要在运行时动态地创建对象和调用方法,这可能会导致性能上的问题。

注意:过度使用装饰器模式可能会导致程序变得复杂,增加系统中类的数量,并可能产生大量小粒度对象,使得代码变得难以维护。因此,在使用装饰器模式时需要谨慎考虑设计是否合适。


http://www.ppmy.cn/embedded/133781.html

相关文章

牛客网刷题(3)(Java的几种常用包)

目录 一、牛客网案例题目。 二、Java常用包的总结。 <1>JAVA常用包&#xff08;图片&#xff09;。 <2>java.lang包。 <3>java.util包。 &#xff08;1&#xff09;集合框架。 1、Collection接口。 2、List接口。 3、Set接口。 4、Queue接口。 5、Map接口。 …

软工毕设开题建议

文章目录 &#x1f6a9; 1 前言1.1 选题注意事项1.1.1 难度怎么把控&#xff1f;1.1.2 题目名称怎么取&#xff1f; 1.2 开题选题推荐1.2.1 起因1.2.2 核心- 如何避坑(重中之重)1.2.3 怎么办呢&#xff1f; &#x1f6a9;2 选题概览&#x1f6a9; 3 项目概览题目1 : 深度学习社…

结合Intel RealSense深度相机和OpenCV来实现语义SLAM系统

结合Intel RealSense深度相机和OpenCV来实现语义SLAM系统是一个非常强大的组合。以下是一个详细的步骤指南&#xff0c;帮助你构建这样一个系统。 硬件准备 Intel RealSense深度相机&#xff1a;例如D415、D435或L515。计算平台&#xff1a;一台具有足够计算能力的计算机&…

JavaEE进阶----18.<Mybatis补充($和#的区别+数据库连接池)>

详解了 1.$和#的区别 2.数据库连接池。 3.简单了解MySQL企业开发规范 一、Mybatis面试题&#xff1a;$和#的区别是什么&#xff1f; MyBatis 参数赋值有两种方式&#xff0c;咱们前面使用了 #{} 进行赋值&#xff0c;接下来我们看下二者的区别。 1.1 #是预编译SQL&#xff0c;$…

mixin的基本用法

目录 一、功能目的二、在Vue项目中&#xff0c;mixin&#xff08;混入&#xff09;有以下几种常见用法&#xff1a;1、代码复用&#xff08;1&#xff09;基础复用示例 2、选项合并&#xff08;1&#xff09;生命周期钩子合并&#xff08;2&#xff09;其他选项合并 3、全局mix…

2022NOIP比赛总结

种花 1.本题是一道前缀和优化加上枚举的问题。先考虑 C 因为 F 是 C 下边随便加一个点&#xff0c;所以只要求出 C 就求出了 F 。 注意到&#xff0c;并没有要求上下行一样&#xff0c;唯一的要求是 C 的两个横要隔一行&#xff0c;这就是问题的突破点&#xff0c;这题很明显…

【MySQL】 运维篇—备份与恢复:备份策略与方法

数据库是存储和管理关键数据的核心&#xff0c;随着数据量的不断增加和业务的不断发展&#xff0c;确保数据的安全性和可恢复性变得至关重要。数据库备份是一种保护措施&#xff0c;可以在数据丢失、损坏或系统故障时&#xff0c;快速恢复数据&#xff0c;确保业务的连续性和稳…

Linux初阶——线程(Part1)

一、线程概念 1、如何理解线程 说到线程&#xff0c;那么我们就要回到进程了。 1.1. 再谈进程 对一个进程来说&#xff0c;它在内存中是这样的&#xff1a; 图1.1-a 其中一个 task_struct 独享一个进程地址空间和一个页表。 而线程其实和进程差不多&#xff0c;是这样的&…