设计模式(三)-结构型模式(3)-装饰模式

一、为何需要装饰模式(Decorator)?

在软件设计中,某个对象会组合很多不同的功能,如果把所有功能都写在这个对象所在的类里,该类会包含很多复杂的代码逻辑,导致代码不美观且难以维护。于是就有了再定义一些新类。这些类负责各自的功能模块,就会实例化一些各司其职的对象。而这些对象再跟原始对象进行组合,以共同完成一个复杂的完整功能。这些对象就称为装饰对象,主要为原对象进行附加功能。有个问题就是,如何把装饰对象跟原对象进行组合的同时,又保证不修改原对象的情况下,进行扩展不同的附加功能。这时候装饰模式就派上用场了。

比如有个推广产品的功能需求:用户看视频后就奖励一个红包,需要做成一个红包生成器。红包具有不同的功能,皮肤换装、显示特效等。根据装饰模式来分析,红包是原对象,作为红包最原始的模样。而皮肤和特效是装饰对象,是为红包装饰的,附加了不同的功能(如具有春节风格功能的皮肤,具有抖动功能的特效)。

针对红包生成器,装饰模式的主要运用方面:
1、用户自定义可选功能: 用户可勾选使用特效功能,弹出时会有抖动的效果。
(这里的用户可分为使用红包的用户,和调用红包生成器底层库接口的程序员)
2、迭代版本可选功能: 在迭代版本中, 我们会对某一模块进行动态地移除或添加功能。如在上个版本中,分析到红包精美的皮肤并不是用户的重要需求,并且传送新皮肤会增加网络资源的成本,反而简洁的界面更能吸引到用户。于是在此版本中可移除掉这个功能,从而不会修改原对象里的代码逻辑。

即装饰模式的主要作用是,用户可以灵活地动态地扩展附加功能,并且不会影响原对象的代码逻辑。

以下是有关红包生成器错误设计的例子:

    //方式一:原对象继承其他多个装饰对象的接口//public interface IRedPacket...//红包,原对象接口//public interface IEffect...//特效//public interface ISkin...//皮肤public class RedPacket: IRedPacket,IEffect, ISkin{public RedPacket() { }public void setPlace()//实现 IRedPacket.setPlace 方法{setSound();//实现 IEffect.setSound 方法setShake();//实现 IEffect.setShake 方法setBgImg();//实现 ISkin.setBgImg 方法//...//如果有移除或扩展某些功能的新需求时,//需要继承或者移除某个基类,就会修改某些实现的方法。}public void setSound() { }public void setShake() { }public void setBgImg() { }//...}//方式二:在原对象所在的类里进行对象组合:// public class Effect:IEffect...//public class Skin:ISkin...public class RedPacket : IRedPacket{private Effect effect;private Skin skin;public RedPacket() { }public void setPlace(){effect.setSound();effect.setShake();skin.setBgImg();//...//如果有移除或扩展某些功能的新需求时,//需要继承或者移除某个装饰对象,就会修改某些代码逻辑。}//...}//不管是在原对象里进行对象组合,还是原对象继承其他对象的基类://当有新需求时,都会对原对象进行修改代码逻辑,从而导致难以扩展和维护的问题。

由以上代码可知,在这两种方式中,如果遇到新需求需要修改时,第一种方式往往会比第二种方式更加的困难,因为多继承关系需要对某些基类的每个方法进行实现。第二种方式也好不了哪里去,因为在原对象里有修改过的痕迹。

特点:

  • 少继承,多组合。(少继承那些装饰类。多组合装饰对象,应放在原对象类的外部进行组合,而不是在原对象类里)

结构:

(以下结构中的抽象组件为抽象类或接口)

  • 原始抽象组件(Component):定义了原始对象和装饰器对象的公共接口。(如红包抽象组件,附加功能的 setPlace 抽象方法作为公共接口)
  • 原始对象具体类(Concrete Component):实现公共接口。(红包具体类实现 setPlace 方法)
  • 装饰抽象组件(Decorator):继承原始对象组件,包装了一个原对象。且定义了与抽象组件公共的接口。(皮肤具体类和特效具体类,都继承于红包抽象组件。)
  • 装饰对象具体类(Concrete Decorator):实现了装饰抽象组件的公共接口,向抽象组件添加新的功能。(红包增加换肤和特效的功能)

适合应用场景特点:

  • 装饰对象对原对象进行附加功能,多个装饰对象跟原始对象进行组合,以实现多个不同的功能模块。(比如一个红包具有换肤和特效的功能)
  • 装饰对象都继承同一个原对象的抽象组件,且每一个装饰对象都包装一个原对象,通过调用公共接口来进行组合。
    (比如红包特效和皮肤都继承于红包,通过公共方法来进行组合)

请添加图片描述

二、例子

需求:

在手机上根据不同的应用场景,会出现具有不同功能和风格的虚拟键盘。在转账界面时,弹出数字模式键盘。在搜索框里,一旦输入一两个字母时就会有以输入字母开头的预知单词浮现。在聊天框里,不仅有预知单词,还有表情包的工具栏。

设计分析:

  • 原始对象为:虚拟键盘。装饰对象为:数字模式、预知单词、表情包。
  • 少继承:把数字模式、预知单词、表情包这些功能单独做成一个装饰器,与原始对象分离。
  • 多组合:组合多个装饰对象,实现同时具有多个不同功能的原始对象。

1、定义原对象抽象接口和装饰对象抽象接口:

//Component:原对象抽象类public interface Ikeyboard{void show();//生成并弹出键盘的公共接口}//Decorator:装饰对象抽象类//方式一:在某些书中,有提到:可以跳过定义此 Decorator 抽象类的步骤,//就直接定义public class NumberMode:Ikeyboard,然后类里包装一个 Ikeyboard 派生实例。//方式二(利用装饰接口,遵循装饰模式的结构)://public interface INumberMode...//定义某个指定的 Decorator 抽象类//public class NumberMode:INumberMode...//类里包装一个 Ikeyboard 派生实例。//先忽略以上方式一和方式二,等理解透了本例子再回头来理解就知道怎么用了。//但为了遵循装饰模式的结构,在这里我还是定义了一个更规范的 Decorator 抽象类。(被用于单继承的基类)public abstract class Decorator : Ikeyboard{protected Ikeyboard ikeyboard;public Decorator(Ikeyboard ikeyboard){this.ikeyboard = ikeyboard;}public virtual void show() { }//添加其他抽象方法或实现的方法...}

2、定义原对象具体类和装饰对象具体类:

//ConcreteComponent:原对象具体类(键盘)public class Keyboard : Ikeyboard{public Keyboard() { }public void show() { }}//ConcreteDecorator:装饰对象具体类(数字模式键盘)public class NumberMode : Decorator{public NumberMode(Ikeyboard ikeyboard):base(ikeyboard){base.ikeyboard = ikeyboard;}public override void  show() { ikeyboard.show(); Console.WriteLine("切换为数字模式键盘."); }}//ConcreteDecorator:装饰对象具体类(预知单词)public class PresetWords : Decorator{public PresetWords(Ikeyboard ikeyboard) : base(ikeyboard){base.ikeyboard = ikeyboard;}public override void show() { ikeyboard.show(); Console.WriteLine("增加了预知单词功能."); }}//ConcreteDecorator:装饰对象具体类(表情包工具)public class EmojiPack : Decorator{public EmojiPack(Ikeyboard ikeyboard) : base(ikeyboard){base.ikeyboard = ikeyboard;}public override void show() { ikeyboard.show(); Console.WriteLine("增加了表情包工具."); }}//还可扩展其他功能,如//public class TouchKeypad : Decorator...//手写键盘装饰类//public class QuickPhrases: Decorator...//快捷短语装饰类...

3、主程序:

class Program{static void Main(string[] args){Ikeyboard keyboard, numberMode, presetWords, preWords, chatKeyboard;//原对象keyboard = new Keyboard();//1.在转账时,弹出附带数字键盘模式的键盘numberMode = new NumberMode(keyboard);Console.WriteLine("\n在转账时:");numberMode.show();//2.在搜索框里,弹出附带预知单词的键盘presetWords = new PresetWords(keyboard);Console.WriteLine("\n在搜索框里:");presetWords.show();//3.在聊天框里,弹出既有预知单词,又有表情包功能的键盘preWords = new PresetWords(keyboard);chatKeyboard = new EmojiPack(preWords);//preWords 里有 PresetWords 功能Console.WriteLine("\n在聊天框里:");chatKeyboard.show();//chatKeyboard 里有 EmojiPack 和 PresetWords 的功能。Console.ReadLine();}}

三、半透明装饰模式。

以上关于虚拟键盘的例子,是属于透明装饰模式。在软件编程的领域中,所谓透明的意思就是用户只需要调用附加功能的公共方法,而无须知道某个装饰对象里的一些具体公开行为。而半透明的意思就是用户只可以知道指定装饰对象的一些具体公开行为。

通俗地来说,就像是某家店的玻璃门。假定玻璃门的门把手是透明的,在门外急性子的顾客因为没法找到门把手,冒然冲进去必定撞得头破血流。而这个门把手如果是半透明的,有一层灰色罩着,门外的顾客会选择先用门把手拉门再进去。(这里的开门行为属于作为玻璃门的门把手这个装饰对象的,顾客可以直接调用这装饰对象的开门功能)

与透明相比,除了附加功能之外,用户还可以对指定的装饰对象动态地进行处理比较详细的逻辑。

半透明装饰模式的例子

需求:

大家协同开发一个客户管理系统,分工负责各自的任务。程序员A负责对某些编辑框进行封装。其中有一个显示客户信息的界面,需要封装一种编辑框用来输入客户的联系电话。然后把编辑框模块提供给另一个程序员B使用。程序员B可以对该编辑框的输入设置限制为11位数字的手机号格式。可复制该内容,但不可把其他内容粘贴在此处。

设计分析:

  • 原对象为:编辑框。装饰对象为:联系电话
  • 装饰对象半透明模式:可对联系电话限制为11位数字,可复制该内容,但不可从别处的内容粘贴到联系电话里。
//Component:原对象抽象类(编辑框接口)public interface ITextEdit{void create();}ConcreteComponent:原对象具体类(编辑框)public class TextEdit : ITextEdit{public TextEdit() { }public void create() { }}//Decorator:装饰对象抽象类public abstract class AbsTelephoneDecorator : ITextEdit{protected ITextEdit iTextEdit;public AbsTelephoneDecorator(ITextEdit iTextEdit){this.iTextEdit = iTextEdit;}public virtual void create() { }public abstract void setNumberLength(int len);public abstract void setCanCopy(bool isCanCopy);public abstract void setCanPaste(bool isCanPaste);}//ConcreteDecorator:装饰对象具体类(联系电话)public class TelephoneDecorator : AbsTelephoneDecorator{public TelephoneDecorator(ITextEdit iTextEdit) :base(iTextEdit){base.iTextEdit = iTextEdit;}public override void create() { iTextEdit.create(); Console.WriteLine("手机号的编辑框创建完成."); }public override void setNumberLength(int len){Console.WriteLine($"set NumberLength:{len}");}public override void setCanCopy(bool isCanCopy){Console.WriteLine($"IsCanCopy:{isCanCopy}");}public override void setCanPaste(bool isCanPaste){Console.WriteLine($"IsCanPaste:{isCanPaste}");}}//主程序class Program{static void Main(string[] args){//半透明装饰模式,用户可以对指定装饰对象里的一些行为进行处理。//原对象ITextEdit textEdit = new TextEdit();//装饰对象AbsTelephoneDecorator telephone = new TelephoneDecorator(textEdit);//用户设置装饰对象的一些行为telephone.setNumberLength(11);telephone.setCanCopy(true);telephone.setCanPaste(false);telephone.create();//透明装饰模式ITextEdit telephone2 = new TelephoneDecorator(textEdit);//telephone2 无法提供装饰对象的 setNumberLength setCanCopy setCanPaste。Console.ReadLine();}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/235142.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytest自动化测试 - 必知必会的一些插件

Pytest拥有丰富的插件架构,超过800个以上的外部插件和活跃的社区,在PyPI项目中以“ pytest- *”为标识。 本篇将列举github标星超过两百的一些插件进行实战演示。 插件库地址:http://plugincompat.herokuapp.com/ 1、pytest-html&#xff1…

【物联网无线通信技术】WiFi从理论到实践(ESP8266)

文章从理论基础到具体实现完整的介绍了最常见的物联网无线通信技术:WiFi。 文章首先介绍了WiFi这种无线通信技术的一些基本概念,并针对其使用的802.11协议的基本概念与其定义的无线通信连接建立过程进行了简单的介绍,然后对WiFi开发常常涉及的…

【强化学习】Deep Q Learning

Deep Q Learning 在前两篇文章中,我们发现RL模型的目标是基于观察空间 (observations) 和最大化奖励和 (maximumize sum rewards) 的。 如果我们能够拟合出一个函数 (function) 来解决上述问题,那就可以避免存储一个 (在Double Q-Learning中甚至是两个…

git的使用思维导图

源文件在github主页:study_collection/cpp学习/git at main stu-yzZ/study_collection (github.com)

Unity的UI界面——Text/Image

编辑UI界面时,要先切换到2d界面 (3d项目的话) 1.Text控件 Text控件的相关属性: Character:(字符) Font:字体 Font Style:字体样式 Font Size:字体大小 Line Spac…

华清远见嵌入式学习——ARM——作业1

要求: 代码: mov r0,#0 用于加mov r1,#1 初始值mov r2,#101 终止值loop: cmp r1,r2addne r0,r0,r1addne r1,r1,#1bne loop 效果:

用户管理第2节课--idea 2023.2 后端规整项目目录

目的:当项目文件多了之后,咱们也能够非常清晰的去找到代码的一个目录 一、项目规整了两大处 1.1 com.yupi.usercenter & resources 二、具体操作 com.daisy.usercenter 2.1 原版 & 鱼皮有出入,demos.web就不删除了 原因&#…

Aurora8B10B(二) 从手册和仿真学习Aurora8B10B

一. 简介 在上篇文章中,主要结合IP配置界面介绍了一下Aurora8B10B,这篇文章将结合文档来学习一下Aurora8B10B内部的一些细节 和 相关的时序吧。文档主要是参考的是这个 pg046-aurora-8b10b-en-us-11.1 二. Aurora8B10B内部细节 在手册上,对…

VR全景是什么?普通人该如何看待VR全景创业?

如果你还没有开始了解VR,那么不妨驻足几分钟细致的了解一下,你就会对VR全景行业有不一样的看法。VR全景与普通的平面图片和视频相比,具有更加丰富的视觉体验和交互性,基于真实场景的全景图像的虚拟现实技术,制作流程简…

Maven仓库上传jar和mvn命令汇总

目录 导入远程仓库 命令结构 命令解释 项目pom 输入执行 本地仓库导入 命令格式 命令解释 Maven命令汇总 mvn 参数 mvn常用命令 web项目相关命令 导入远程仓库 命令结构 mvn deploy:deploy-file -Dfilejar包完整名称 -DgroupIdpom文件中引用的groupId名 -Dartifa…

Ubuntu 常用命令之 apt-get 命令用法介绍

apt-get是Ubuntu系统下的一个命令行工具,用于处理包。这个命令可以自动下载和安装软件包及其依赖项。它是Advanced Packaging Tool (APT)的一部分,APT是处理包的高级工具,可以处理复杂的包关系,如依赖关系等。 apt-get命令的常见…

一个真正的软件测试从业人员必备技能有哪些?

协同开发能力: 1. 项目管理(SVN、Git) 2. 数据分析能力(Fiddler、Charles、浏览器F12)。 接口测试: 1. 概念及接口测试原理概念(概念、接口测试原理) 2. 接口测试工具&#xff…

数据工作者最爱的AI功能,你知道吗~

在工作中难以避免的一项任务就是各种数据总结和汇报,怎么分析总结?以何种形式汇报?都是具有一定的难点,所以我要推荐的就是具有AI图表解析功能的可视化工具——Easyv数字孪生低代码可视化平台。可实现对数据的可视化展示&#xff…

软件测试项目测试报告总结

测试计划概念:就在软件测试工作实施之前明确测试对象,并且通过资源、时间、风险、测试范围和预算等方面的综合分析和规划,保证有效的实施软件测试。 需求挖掘的6个方面: 1、输入方面 2、处理方面 3、结果输出方面 4、性能需求…

linux 驱动——杂项设备驱动

杂项设备驱动 在 linux 中,将无法归类的设备定义为杂项设备。 相对于字符设备来说,杂项设备的主设备号固定为 10,而字符设备不管是动态分配还是静态分配设备号,都会消耗一个主设备号,比较浪费主设备号。 杂项设备会自…

uml用例图是什么?有哪些要素?

UML用例图是什么? UML用例图(Unified Modeling Language Use Case Diagram)是一种用于描述系统功能和用户之间交互的图形化建模工具。它是UML的一部分,主要用于识别和表示系统中的各个用例(用户需求或功能点&#…

鸿蒙开发之压缩/解压缩

本次学习遗留一个问题:压缩/解压缩的路径怎么获取??希望知道的小伙伴能给说一下,私聊评论皆可。 一、API使用 代码相对来说比较简单 //需要导入的头文件 import zlib from ohos.zlib//压缩函数 function zipFile() {let rawfil…

高通平台开发系列讲解(USB篇)adb应用adbd分析

沉淀、分享、成长,让自己和他人都能有所收获!😄 在apps_proc/system/core/adb/adb_main.cpp文件中main()函数会调用adb_main()函数,然后调用uab_init函数 在uab_init()函数中,会创建一个线程,在线程中会调用init_functionfs()函数,利用ep0控制节点,创建ep1、ep2输…

在区块链中看CHAT的独特见解

问CHAT:谈谈对区块链以及区块链金融的理解 CHAT回复:区块链是一种去中心化的分布式数据库技术,这种技术通过加密算法,使数据在网络中传输和存储的过程变得更加安全可靠。区块链的出现引领了存储、交易等形式的革命,改变…

通过https协议访问Tomcat部署并使用Shiro认证的应用跳转登到录页时协议变为http的问题

问题描述: 在最近的一个项目中,有一个存在较久,并且只在内部城域网可访问的一个使用Shiro框架进行安全管理的Java应用,该应用部署在Tomcat服务器上。起初,应用程序可以通过HTTP协议访问,一切运行都没…