设计模式之装饰模式
定义
装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
模式特点
(1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对象相同的方式和装饰对象交互。
(2) 装饰对象包含一个真实对象的引用(reference)
(3) 装饰对象接受所有来自客户端的请求。它把这些请求转发给真实的对象。
(4) 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
适用性
以下情况使用Decorator模式
1. 需要扩展一个类的功能,或给一个类添加附加职责。
2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
优点
1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。
2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
缺点
1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。
2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。
3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。
设计原则
1. 多组合,少继承。
利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。
2. 类应设计的对扩展开放,对修改关闭。
在装饰模式中的各个角色有:
(1)抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
(2)具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
(3)装饰(Decorator)角色:持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
(4)具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。
情景假设
变形金刚在变形之前是一辆汽车,它可以在陆地上移动。当它变成机器人之后除了能够在陆地上移动之外,还可以说话;如果需要,它还可以变成飞机,除了在陆地上移动还可以在天空中飞翔。
情景大概是由汽车拓展成机器人和飞机时,该怎么处理?
情景分析
关于上面情景的类图(具体分析在下面)

首先我们很容易想到的是,既然要拓展嘛,那就先继承汽车拥有汽车的方法,然后再在继承类上加上机器人特定的方法,那就是机器人啦,加上汽车特有的方法,那就是汽车类啦。一切都很简单,但是论实际上,增加一种拓展方式,很可能增加非常多的是实现类,这将会让系统变得非常的庞大(树形的深度越来越深,互相耦合程度更加庞大)。其次可能还会牵扯到多继承的问题(因为有些类可能会继承多个需要拓展的),同时使用继承也是破化了类的封装性
所以装饰模式所采用的方法就是利用关联代替继承,这样一来降低了继承所导致的耦合度增大,同时,软件维护上,关联关系的松耦合性,使得系统更加的容易维护。树形的层数较少,在于横向的拓展,拓展起来更加方便,还能够很好的实现运行时动态拓展。
所以先定义抽象类
//抽象构件类
public abstract class Transform
{public abstract void move();
}
具体已有的汽车类
//汽车Car
public final class Car extends Transform{public void Car(){System.out.println("变形金刚是一辆车!");}public void move(){System.out.println("在陆地上移动!");}
}
细心的同学发现了,我还定义了final,声明着这不能继承拓展增加难度,这样其实我们就不得不利用关联的方式了。
定义一个装饰类,装饰类同样继承抽象类Transform,因为装饰类的本质我们不能忘,我们只是对原有存在的进行拓展,而对于客户端而言应当还是能当作之前的情况继续使用,为了实现统一的使用,实现同一的抽象类客户端便能够可以无视细节。在这个时候我们可以增加需要被拓展的类作为属性关联起来。
//装饰类Changer
public class Changer extends Transform{private Transform t ;public void add(Transform t){this.t = t;}public void move(){t.move();//调用关联类自己的方法}
}
接下来就是机器人类和飞机类,继承装饰器类即可
public class Robot extends Changer{public Robot(Transform transform){super(transform);System.out.println("变成机器人!");}public void say(){System.out.println("说话!");}
}public class AirPlane extends Changer{public AirPlane(Transform transform){super(transform);System.out.println("变成飞机!");}public void fly(){System.out.println("在天空飞翔!");}
}
接下来是客户端的代码:
public class Client
{public static void main(String args[]){Transform camaro;camaro = new Car();camaro.move();Robot bumblebee = new Robot(camaro);bumblebee.move();bumblebee.say();}
}
可以看到,我们这里先定义了一个原先的Car类,之后我们再关联起来运用了新的“装饰”的方法。
模式结构及分析
(一) 两种装饰模式
两种装饰模式分别是透明装饰模式和不透明模式。其实这个划分的原因是从客户端的视角来区分的,对于客户端来说是透明的还是不透明的。(其实这里的理解和我们之前讲的组合模式是一样的,我相信如果认真看过我上一篇组合模式的童鞋,应该能秒懂和想象得到我接下来讲的是什么)
半透明装饰模式
咱们看上面的例子的客户端:
public class Client
{public static void main(String args[]){Transform camaro;camaro = new Car();camaro.move();Robot bumblebee = new Robot(camaro);bumblebee.move();bumblebee.say();}
}
我们看到开头两行的代码,其实我们一开始设计就已经封装了抽象类Transform但是在实际中并不是统一的定义,为什么呢?其实是因为我们我们在装饰之后,我们新的装饰类子类多了些和之前不同的全新方法,而这些方法,在抽象类中是没有的,以至于只能在具体装饰类中里面去定义实现。
而这对于客户端来说是不透明的, 我们原本装饰的目的就是,不改变原先的借口进行拓展,增强原有的功能,但是这里因为装饰后而增加了新的功能,以至于我们编写代码的时候不能很好的统一去编写而是要分别定义。但是实际开发中,其实半透明还更常见,因为增加方法其实是常有的事情。但是有个缺点就是不能实现多重装饰。可以看下面透明模式中客户端代码理解
透明装饰模式
在这模式中,针对于客户端操作的统一性,就有了透明装饰模式。
上面的例子我们可以修改一下让大家理解,其实就是只要在最开始的抽象类中就定义好大家共用的方法即可,也即装饰具体类本身也不要去拓展新方法,而是稍微的重构或者补充原方法,那就可以写成透明装饰模式。
//抽象构件类
public abstract class Transform
{public abstract void move();
}
//汽车Car
public final class Car extends Transform{public void Car(){System.out.println("变形金刚是一辆车!");}public void move(){System.out.println("在陆地上移动!");}
}//装饰类Changer
public class Changer extends Transform{private Transform t ;public void add(Transform t){this.t = t;}public void move(){t.move();//调用关联类自己的方法}
}//接下来就是机器人类和飞机类,继承装饰器类即可
public class Robot extends Changer{public Robot(Transform transform){super(transform);System.out.println("变成机器人!");}public void move(){System.out.println("连滚带爬的move!");}
}public class AirPlane extends Changer{public AirPlane(Transform transform){super(transform);System.out.println("变成飞机!");}public void move(){System.out.println("在天空飞翔的移动!");}
}//客户端
public class Client
{public static void main(String args[]){Transform camaro;camaro = new Car();camaro.move();Transform bumblebee;bumblebee = new Robot(camaro);bumblebee.move();Transform plane;plane = new AirPlane(camaro);plane.move();}
}
好的可以看到我们抽象构件类定义了统一的方法,而在装饰子类中我们也只是增强了原来的方法,所以对于客户端我们可以用抽象类去定义然后统一的操作,细心的同学肯定发现了,我这客户端最后定义的plane用的是我新建装饰器具体类,也就是透明模式比半透明模式还好的就是可以实现多重装饰。
(二) 两种组合模式的总结
半透明(Semi-transparent)装饰模式
用具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义
对于客户端而言,具体构件类型无须关心,是透明的;但是具体装饰类型必须指定,这是不透明的
可以给系统带来更多的灵活性,设计相对简单,使用起来也非常方便
客户端使用具体装饰类型来定义装饰后的对象,因此可以单独调用扩展新增的方法
最大的缺点在于不能实现对同一个对象的多次装饰,而且客户端需要有区别地对待装饰之前的对象和装饰之后的对象
透明(Transparent)装饰模式
要求客户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该将对象声明为具体构件类型或具体装饰类型,而应该全部声明为抽象构件类型
对于客户端而言,具体构件对象和具体装饰对象没有任何区别
可以让客户端透明地使用装饰之前的对象和装饰之后的对象,无须关心它们的区别
可以对一个已装饰过的对象进行多次装饰,得到更为复杂、功能更为强大的对象
无法在客户端单独调用新增拓展方法。(只能内嵌使用)
相关文章:
设计模式之装饰模式
定义 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。 模式特点 (1) 装饰对象和真实对象有相同的接口。这样客户端对象就能以和真实对…...
华为OD机试真题 Java 实现【最佳对手】【2023Q1 200分】
一、题目描述 游戏里面,队伍通过匹配实力相近的对手进行对战。但是如果匹配的队伍实力相差太大,对于双方游戏体验都不会太好。 给定 n 个队伍的实力值,对其进行两两实力匹配,两支队伍实例差距在允许的最大差距 d内,则可以匹配。 要求在匹配队伍最多的情况下匹配出的各组…...
IOS证书制作教程
IOS证书制作教程 点击苹果证书 按钮 点击新增 输入证书密码,名称 这个密码不是账号密码,而是一个保护证书的密码,是p12文件的密码,此密码设置后没有其他地方可以找到,忘记了只能删除证书重新制作,所以请务…...
【人工智能】蚁群算法(密恐勿入)
蚁群算法(密恐勿入) 蚁群算法--给你一个感性认识 蚁群算法(密恐勿入)1. 算法简介1.1 基本原理1.1.1 模拟蚂蚁在简单地形,寻找食物1.1.2 模拟蚂蚁在复杂地形,找到食物1.2 算法应用 2. 算法解析3.算法应用——…...
VONR排查指导分享
不能注册或呼叫到SIP服务器端30秒挂断呼叫的黄金法则咬线或摘机状态单通或无语音收到400 bad request收到413,513 Request Entity Too Large或Message Too Large消息收到408, 480或者487 消息483 - Too Many Hops488 – Not Acceptable Here语音质量和思…...
Daftart.ai:人工智能专辑封面生成器
前言 Daft Art AI是一款使用人工智能技术来帮助您制作专辑封面的软件,它可以让您在几分钟内,用简单的编辑器和精选的美学风格,为您的专辑或歌曲创建出惊艳的高质量的艺术品。Daft Art AI有以下几个特点:简单易用:您只…...
ZigBee案例笔记 - 定时器
文章目录 1.片内外设I/O2.定时器简介3.定时器1寄存器4.定时器1操作自由运行模式模模式正计数/倒计数模式 5.16位计数器定时器1控制LED 示例 6.定时器3概述自由运行模式倒计数模式模模式正/倒计数模式 7.定时器3寄存器定时器3控制LED闪烁 1.片内外设I/O 定时器这样的片内外设也…...
GE H201TI 全系统自检和自诊断
Hydran 201Ti是一个小型在线预警发射器。它永久安装在变压器上,将为工作人员提供各种故障气体复合值的单一ppm读数,以提醒他们潜在的问题。 可以下载该值,并且可以将警报设置在预定水平,以提醒人员并能够监控发展中的故障状况。 …...
这个屏幕录制太好用了!
哈喽,大家好!今天给各位小伙伴测试了一屏幕录制的小工具——ApowerREC。它是一款专业同步录制屏幕画面及声音的录屏软件。界面简洁,操作简单,支持实时编辑屏幕录像、创建计划任务、录制摄像头高清视频等功能。废话不多说ÿ…...
初识redis【redis的安装使用与卸载】
一.redis的概念 Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。在redis官网中对redis的描述是这样的&#…...
接口测试总结及其用例设计方法整理,希望可以帮到你
目录 接口测试的总结文档 第一部分: 第二部分: 接口测试用例设计 接口测试的总结文档 第一部分:主要从问题出发,引入接口测试的相关内容并与前端测试进行简单对比,总结两者之前的区别与联系。但该部分只交代了怎么做…...
基于FPGA的多功能数字钟的设计
摘要 数字钟是采用数字电路实现对时、分、秒数字显示的计时装置,是人们日常 生活中不可少的必需品。本文介绍了应用FPGA芯片设计多功能数字钟的•种方 案,并讨讨论了有关使用FPGA芯片和VHDL语言实现数字钟设计的技术问题。 关键词数字钟、分频器、译码器、计数器、校时电路、…...
第四十二天学习记录:C语言进阶:笔试题整理Ⅲ
问:解释一下int(*a[20])(int)是什么? ChatAI答: int (*a[20])(int) 是一个数组,该数组中每个元素都是一个指向函数的指针,该函数具有一个int类型的参数,并返回一个int类型的值。 具体来说,a是一…...
GLSL 代码规范
文件 文件顶点,片段,几何和计算着色器文件应该分别有 _vert, _frag, geom 和 _comp 后缀(例如: eevee_film_fragg.glsl)。Shader文件名必须是唯一的,并且必须以它们所属的模块作为前缀(例如: workbench_material_lib.glsl eevee_film_lib.glsl)。一个 shader 文件必须包含且…...
红黑树封装map和set
文章目录 红黑树封装map和set1. 改良红黑树1.1 改良后的节点1.2 改良后的类分别添加仿函数代码 3. 封装map和set3.1 set3.2 map 3. 迭代器3.1 begin 和 end3.2 operator()和operator--()3.3 const迭代器set的迭代器map的迭代器 4. map的operator[]的重载5. 完整代码实现5.1 RBT…...
python序列
在Python中,序列类型包括字符串、列表、元组、集合和字典,这些序列支持以下几种通用的操作,但比较特殊的是,集合和字典不支持索引、切片、相加和相乘操作。 字符串也是一种常见的序列,它也可以直接通过索引访问字符串内…...
LeetCode35. 搜索插入位置(二分法入门)
写在前面: 题目链接:LeetCode35. 搜索插入位置 编程语言:C 题目难度:简单 一、题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会…...
macOS Ventura 13.4 RC3(22F66)发布
系统介绍 5 月 17 日消息,苹果今日向 Mac 电脑用户推送了 macOS 13.4 RC 3 更新(内部版本号:22F66),本次更新距离上次发布隔了 5 天。 macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力等功能。其中&…...
CSI和DSI介绍
1、CSI和DSI的接触协议介绍 MIPI、CSI、CCI 协议基础介绍_csi协议_赵哈哈x的博客-CSDN博客 流媒体技术基础-摄像头接口与标准_【零声教育】音视频开发进阶的博客-CSDN博客 《摄像头 —— MIPI CSI-2简介》 USB摄像头使用 — Lichee zero 文档 2、AIO-3288J ,and…...
vue3+antDesignVue前端纯导出
效果 <a-buttonsize"default"style"margin-left: 10px"click"exportData">导出</a-button>1.下载所需依赖 npm install xlsx --save npm install file-saver --save<script setup> import { reactive, ref } from "vue…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
