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

news2024/9/17 8:29:46

一、为何需要装饰模式(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.coloradmin.cn/o/1326156.html

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

相关文章

腾讯AI Lab C++开发日常实习 一面

我们是校企联合专业(深大腾班),所以腾讯给了我们这个实习的机会,据说面试比一般日常实习的面试简单,记录人生第一次实习面试 上来先自我介绍 我介绍了学校专业和求职意向和开发经历,问没了? …

文具品牌企业网站建设的作用是什么

文具的应用非常广泛,不仅是学生、有些行业也会频繁使用,市场中大小文具品牌也是比较多,对文具品牌商和大经销商而言,批发远比零售更好,但在实际经营中,却也面临不少痛点: 1、拓客难 中小品牌商…

【解刊】1个月录用,18天见刊!CCF-A类顶刊,中科院基金委主办,国人占比97%!

计算机类 • 好刊解读 今天小编带来Springer旗下计算机领域顶刊,高分区高影响因子,新晋CCF-A类推荐,如您有投稿需求,可作为重点关注!后文有相关领域真实发表案例,供您投稿参考~ 01 期刊简介 Science Chi…

重生奇迹MU觉醒战士攻略

剑士连招技巧:生命之光:PK前起手式,增加血上限。 雷霆裂闪:眩晕住对手,剑士PK战士第一技能,雷霆裂闪是否使用好关系到胜负。 霹雳回旋斩:雷霆裂闪后可以选择用霹雳回旋斩跑出一定范围(因为对手…

springboot云HIS医院信息管理系统源码

通过云HIS平台,可以减少医院投资,无需自建机房和系统,快速实现信息化服务。系统升级及日常维护服务有云平台提供,无需配备专业IT维护人员进行系统维护。 一、his系统和云his系统的区别 His系统和云his系统是两种不同的计算平台,它们在技术架构上存在很大的差异。下…

【jvm从入门到实战】(九) 垃圾回收(2)-垃圾回收器

垃圾回收器是垃圾回收算法的具体实现。 由于垃圾回收器分为年轻代和老年代,除了G1之外其他垃圾回收器必须成对组合进行使用 垃圾回收器的组合使用关系图如下。 常用的组合如下: Serial(新生代) Serial Old(老年代) Pa…

免 费 搭 建 小程序商城,打造多商家入驻的b2b2c、o2o、直播带货商城

在数字化时代,电商行业正经历着前所未有的变革。鸿鹄云商的saas云平台以其独特的架构和先进的理念,为电商行业带来了全新的商业模式和营销策略。该平台涉及多个平台端,包括平台管理、商家端、买家平台、微服务平台等,涵盖了pc端、…

java定义三套场景接口方案

一、背景 在前后端分离开发的背景下,后端java开发人员现在只需要编写接口接口。特别是使用微服务开发的接口。resful风格接口。那么一般后端接口被调用有下面三种场景。一、不需要用户登录的接口调用,第二、后端管理系统接口调用(需要账号密…

【进阶篇】YOLOv8实现K折交叉验证——解决数据集样本稀少和类别不平衡的难题,让你的模型评估更加稳健

YOLOv8专栏导航:点击此处跳转 K折交叉验证 K折交叉验证(K-Fold Cross-Validation)是一种常用的机器学习模型评估方法,可以帮助我们评估模型的性能,特别适用于数据集相对较小的情况。 在K折交叉验证中,将原…

JMeter接口测试高阶——精通JMeter接口测试之BeanShell及调用java和python脚本

文章目录 一、BeanShell组件二、BeanShell自带的语法(BeanShell常用变量和语法)1.log打印2.vars用来操作JMeter的局部变量(只能在一个线程组里面使用的变量)3.props用来操作JMeter的全局变量(能够跨线程组取值的变量&a…

2023 英特尔On技术创新大会直播 | 边云协同加速 AI 解决方案商业化落地

目录 前言边云协同时代背景边缘人工智能边缘挑战英特尔边云协同的创新成果最后 前言 最近观看了英特尔On技术创新大会直播,学到了挺多知识,其中对英特尔高级首席 AI 工程张宇博士讲解的边云协同加速 AI 解决方案商业化落地特别感兴趣。张宇博士讲解了英…

python实现一个图片查看器——可拖动、缩放和颜色画笔

目录 0 前言1 准备工作2 窗口布局3 图片显示功能3 图片拖拽功能4 图片缩放功能(难度大)5 画笔功能6 颜色选择功能后记源码 0 前言 在现如今的数字时代,我们对于图片的需求越来越大。无论是在工作中,还是在日常生活中,…

SLAM算法与工程实践——SLAM基本库的安装与使用(6):g2o优化库(1)g2o库的安装

SLAM算法与工程实践系列文章 下面是SLAM算法与工程实践系列文章的总链接,本人发表这个系列的文章链接均收录于此 SLAM算法与工程实践系列文章链接 下面是专栏地址: SLAM算法与工程实践系列专栏 文章目录 SLAM算法与工程实践系列文章SLAM算法与工程实践…

PostGIS轨迹分析——横跨180°经线

问题描述 在处理AIS数据中,经常会遇到轨迹线横穿180经线的情况,这种数据绘制到地图上显示的非常乱,如下图所示: 数据模拟 在geojson.io上模拟一条轨迹线,可以看到轨迹显示的非常好,红框里面的经纬度超过…

使用Alpha Vantage API和Python进行金融数据分析

Alpha Vantage通过一套强大且开发者友好的数据API和电子表格,提供实时和历史的金融市场数据。从传统资产类别(例如股票、ETF、共同基金)到经济指标,从外汇汇率到大宗商品,从基本数据到技术指标,Alpha Vanta…

HashSet使用-力扣349做题总结

349. 两个数组的交集 分析代码HashSet出错的知识点1、HashSet新建2、HashSet添加add3、是否包含某元素4、集合->数组5、增强for循环 分析 没做出来的原因代码随想录的视频文字学习 为什么没做出来,因为没有理解好题意。根据示例1可知是去重的。且题目明确说“不考…

机器学习算法(12) — 集成技术(Boosting — Xgboost 分类)

一、说明 时间这是集成技术下的第 4 篇文章,如果您想了解有关集成技术的更多信息,您可以参考我的第 1 篇集成技术文章。 机器学习算法(9) - 集成技术(装袋 - 随机森林分类器和...... 在这篇文章中,我将解释…

先进制造身份治理现状洞察:从手动运维迈向自动化身份治理时代

在新一轮科技革命和产业变革的推动下,制造业正面临绿色化、智能化、服务化和定制化发展趋势。为顺应新技术革命及工业发展模式变化趋势,传统工业化理论需要进行修正和创新。其中,对工业化水平的判断标准从以三次产业比重标准为主回归到工业技…

服务器数据恢复-昆腾存储StorNext文件系统下raid5数据恢复案例

服务器数据恢复环境: 昆腾某型号存储,StorNext文件存储系统。 共有9个分别配置了24块磁盘的磁盘柜,其中8个磁盘柜存放普通数据,1个磁盘柜存放元数据。 存放元数据的磁盘柜中的24块磁盘组建了8组RAID1阵列和1组4盘RAID10阵列&#…