23种设计模式之装饰模式

2023-11-18

装饰模式

一个简陋的房子,它可以让人在里面居住,为人遮风避雨,但如果给它进行装修,那么它的居住环境就更加宜人了。程序中的对象也与房子十分类似,首先有一个相当于“房子”的对象,然后经过不断装饰,不断对其增加功能,它就变成了使用功能更加强大的对象。像这样不断的给对象添加功能的模式称为装饰模式

一、装饰模式概述

在软件设计中,装饰模式是一种用于替代继承的技术,通过无需定义子类的方式动态地增强对象的功能,使用对象间关联关系替代类之间的继承关系。在装饰模式中,引入了装饰类,装饰类不仅可以调用原有类中的方法,还可以添加新的方法以扩展原有类的功能。

装饰模式定义:动态地给一个对象增加一些职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。

二、装饰模式结构与实现

2.1、装饰模式结构

  • 装饰模式UML类图

在这里插入图片描述

装饰模式包含以下四个角色:

  • Component(抽象构件类):该角色通常是抽象类或者接口,它是具体构件类和抽象装饰类的共同父类,声明了具体构件类需要实现的方法,它的引入可以使客户端一致地处理未被装饰的对象以及装饰之后的对象,实现对客户端的透明操作。
  • ConcreteComponent(具体构件类):该角色是抽象构件类的子类,实现了在抽象构件类中声明的方法,装饰类可以给该角色添加职责。
  • Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件类添加新功能,但具体功能实现交由其子类完成。它还维护了一个指向抽象构件类的引用,通过这个引用可以调用装饰前对象的原有方法,通过子类扩展这个方法,来达到给具体构件类装饰的效果。
  • ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的功能。具体装饰类可以调用抽象装饰类中定义的方法,还可以添加新的方法,用以扩展对象的行为。

2.2、装饰模式实现

抽象模式中各角色的一些典型代码如下:

  • 抽象构件类

    抽象构件类通常是抽象类或接口,在其中声明抽象业务方法,也可以具体实现一些在具体构件类中共有的业务方法。

    public abstract class Component{
        public abstract void operation();
    }
    
  • 具体构件类

    具体构件类实现在抽象构建类中定义的抽象业务方法,通常只是提供最基本功能的实现,复杂的功能通过装饰类来实现。

    public class ConcreteComponent extends Component{
        public void operation(){
            // 基本功能实现
        }
    }
    
  • 抽象装饰类

    抽象装饰类是装饰模式中的核心设计,它是抽象构件类的子类,并将抽象构件类对象聚合进来,实现了在抽象构件类中定义的方法,只不过仅仅在重写方法中调用构件对象的原有业务方法,具体的装饰过程交由子类完成。

    public abstract class Decorator extends Component{
        private Component component;
        public Decorator(Component component){
            this.component = component;
        }
        
        public void operation(){
            component.operation();
        }
    } 
    
  • 具体装饰类

    具体装饰类是抽象装饰类的子类,将继承operation方法,可以根据需要对该方法进行扩展。典型代码如下:

    public class ConcreteDecorator extends Decorator{
        public ConcreteDecorator(Component component){
            super(component);
        }
        public void operation(){
            super.operation(); // 调用原有业务方法
            this.addNewBehavior(); // 调用新增业务方法
        }
        // 新增业务方法
        public void addNewBehavior(){
            // ...
        }
    }
    

因为在抽象装饰类中注入的抽象构件类对象,因此就还可以将一个已经装饰过的对象(即具体装饰类对象)注入其中进行多层装饰。

三、应用实例

  • 应用案例说明:现在有一套图形界面构件库,这个构件库提供了大量的基本构件,如窗体(Window)、文本框(TextBox)、列表框(ListBox)等,但在使用过程中,用户经常会提出一些定制需求,比如一个带滚动条的窗体、一个带黑色边框的文本框或一个既带有滚动条又带有黑色边框的列表框等。现在通过装饰模式来实现这个案例。

  • UML类图
    在这里插入图片描述

  • 示例代码:

    • Component:抽象界面构件类,充当抽象构件类的角色

      public interface Component {
         /**
          * 展示界面
          */
         void display();
      }
      
    • Window:窗体类,充当具体构件类角色

      public class Window implements Component {
      
         @Override
         public void display() {
            System.out.println("显示窗体!");
         }
      }
      
    • TextBox:文本框类,充当具体构件类角色

      public class TextBox implements Component {
      
         @Override
         public void display() {
            System.out.println("显示文本框!");
         }
      }
      
    • ListBox:列表框类,充当具体构件类角色

      public class ListBox implements Component {
      
         @Override
         public void display() {
            System.out.println("显示列表框!");
         }
      }
      
    • ComponentDecorator:构件装饰类,充当抽象装饰类角色

      public abstract class ComponentDecorator implements Component {
         private Component component;
      
         public ComponentDecorator(Component component) {
            this.component = component;
         }
      
         /**
          * 仅调用抽象构建类的display方法
          */
         @Override
         public void display() {
            component.display();
         }
      }
      
    • ScrollBarDecorator:滚动条装饰类,充当具体装饰类角色

      public class ScrollBarDecorator extends ComponentDecorator{
      
         public ScrollBarDecorator(Component component) {
            super(component);
         }
      
         @Override
         public void display() {
            this.setScrollBar();
            super.display();
         }
      
         /**
          * 装饰方法
          */
         public void setScrollBar(){
            System.out.println("为构件添加滚动条!");
         }
      }
      
    • BlackBorderDecorator:黑色边框装饰类,充当具体装饰类角色

      public class BlackBorderDecorator extends ComponentDecorator{
      
         public BlackBorderDecorator(Component component) {
            super(component);
         }
      
         @Override
         public void display() {
            this.setBlackBorder();
            super.display();
         }
      
         /**
          * 装饰方法,设置黑色边框
          */
         public void setBlackBorder(){
            System.out.println("为构件添加黑色边框!");
         }
      }
      
    • App:客户端测试类

      public class App {
         public static void main(String[] args) {
            Component component, componentSB, componentBB;
            component = new Window();
            componentSB = new ScrollBarDecorator(component);
            // 增强原有构件功能-->添加滚动条
            componentSB.display();
            System.out.println("===================");
            // 继续增强-->添加滚动条,添加黑色边框
            componentBB = new BlackBorderDecorator(componentSB);
            componentBB.display();
         }
      }
      
    • 执行结果
      在这里插入图片描述

如果需要添加新的具体构件类或者新的具体装饰类对象,只需要将它们作为抽象构件类或抽象装饰类的子类即可,不需要改变现有系统的任何代码,符合开闭原则!

四、装饰模式分类

在上述案例中,客户端可以完全面向抽象编程,不用关心任何具体构件类或者具体装饰类,也即具体构件类或者具体装饰类对于客户端来说是透明的,因此这种装饰模式被称为透明装饰模式。其好处就是客户端可以面向抽象编程,但是在客户端想要单独调用装饰类的新增功能的场景下,这种模式就无法满足需要。这样,客户端不能一致地对待装饰前和装饰后的对象。半透明装饰模式要求装饰类的引用必须是具体装饰类类型,但因为具体构件类对象的引用仍然可以是抽象构件类类型,因此这种装饰模式就被称为半透明装饰模式。

  • 透明装饰模式示例代码

    Component component, componentSB, componentBB;
    component = new Window();
    componentSB = new ScrollBarDecorator(component);
    // 增强原有构件功能-->添加滚动条
    componentSB.display();
    System.out.println("===================");
    // 继续增强-->添加滚动条,添加黑色边框
    componentBB = new BlackBorderDecorator(componentSB);
    componentBB.display();
    
  • 半透明装饰模式示例代码

    // 半透明装饰模式
    Component component = new Window();
    ScrollBarDecorator decoratorSB = new ScrollBarDecorator(component);
    decoratorSB.display();
    // 单独调用具体装饰类的新增方法
    decoratorSB.setScrollBar();
    System.out.println("===================");
    BlackBorderDecorator decoratorBB = new BlackBorderDecorator(decoratorSB);
    decoratorBB.display();
    

五、装饰模式优缺点与适用环境

装饰模式降低了系统的耦合度,可以动态的添加或删除原有对象的职责,使具体装饰类和具体构件类可以独立变化、新增,减少类爆炸,提高系统的可扩展性和可维护性,装饰模式是取代继承复用的有效方式之一。

  • 优点
    • 可以动态的给对象添加新功能,通过运行时选择不同的具体装饰类给对象添加不同的新功能
    • 使用对象关联方式替代继承复用方式,符合合成复用原则,大大减少了类爆炸
    • 可以进行多次装饰,通过对具体装饰类进行排列组合,可以创造出很多种不同的新行为
    • 具体构件类和具体装饰类可以独立变化,扩展新功能时,不需要修改系统现有代码,符合开闭原则。
  • 缺点
    • 装饰模式会生成大量的功能类似的小对象(具体装饰类)
    • 装饰模式可以进行多次装饰达到对原有对象功能的增强,但在排查、定位问题时也会特别麻烦,需要逐级排查,提高问题排查成本。
  • 适用环境
    • 希望以透明、动态的方式给单个对象添加职责
    • 不能通过继承的方式对现有系统的类进行功能扩展时,可以考虑使用装饰模式进行功能扩展。不能使用继承方式的原因有两大类:一是系统现有大量独立的类,如果通过继承方式进行扩展,可能会出现类爆炸;二是原有类被final关键字修饰,无法修改。

六、小结

  • 装饰模可以动态地给一个对象增加一些职责。就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案

  • 装饰模式包含抽象构件、具体构件、抽象装饰类和具体装饰类四个角色

  • 装饰模式分为透明装饰模式和半透明装饰模式。透明装饰模式下,客户端可以一致地对待装饰前和装饰后对象;半透明装饰模式下,客户端可以单独调用具体装饰类的新功能

  • 装饰模式优点是可以动态给对象添加新功能、减少系统类爆炸、扩展功能符合开闭原则。缺点是会生成大量小对象,一定程度上会影响系统的性能,还会增加系统的排查问题成本

  • 当希望可以动态、透明地给对象添加功能或不能通过继承方式扩展类功能时,可以考虑使用装饰模式。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

23种设计模式之装饰模式 的相关文章

  • 方法返回类型前的 是什么意思?

    下面的方法返回一个List组成T类型元素 public
  • Java将字符串解析为double

    如何解析字符串中的这个 Double 00034800 变成 Double 值 最后两位数字实际上是小数点 所以我正在寻找的结果是348 00 是否有这样的格式可以与十进制格式一起使用 Well String s 00034800 doub
  • 为什么在 10 个 Java 线程中递增一个数字不会得到 10 的值?

    我不明白 a 的值为0 为什么 a 不是10 那段代码的运行过程是怎样的 是否需要从Java内存模型来分析 这是我的测试代码 package com study concurrent demo import lombok extern sl
  • 我们可以有条件地声明 spring bean 吗?

    有没有一种方法可以有条件地声明 Spring bean 例如
  • 通过Zuul上传大文件

    我在通过 zuul 上传大文件时遇到问题 我正在使用 apache commons 文件上传 https commons apache org proper commons fileupload https commons apache o
  • 无法使用 datastax java 驱动程序通过 UDT 密钥从 cassandra 检索

    我正在尝试使用用户定义的类型作为分区键将对象存储在 cassandra 中 我正在使用 datastax java 驱动程序进行对象映射 虽然我能够插入到数据库中 但无法检索该对象 如果我更改分区键以使用非 udt 例如文本 我就能够保存和
  • 我对线程失去了理智

    我想要这个类的对象 public class Chromosome implements Runnable Comparable
  • Java中Gson、JsonElement、String比较

    好吧 我想知道这可能非常简单和愚蠢 但在与这种情况作斗争一段时间后 我不知道发生了什么 我正在使用 Gson 来处理一些 JSON 元素 在我的代码中的某个位置 我将 JsonObject 的 JsonElements 之一作为字符串获取
  • java中如何知道一条sql语句是否执行了?

    我想知道这个删除语句是否真的删除了一些东西 下面的代码总是执行 else 是否删除了某些内容 执行此操作的正确方法是什么 public Deleter String pname String pword try PreparedStatem
  • JOOQ 忽略具有默认值的数据库列

    看来JOOQ完全忽略了数据库列的默认值 既不会更新 ActiveRecord 对象 也不会在 INSERT 时跳过此列 相反 它尝试将其设置为 NULL 这在 NOT NULL 列上失败 Example CREATE TABLE bug f
  • getCurrentSession 在网络中休眠

    我正在使用 hibernate 和 jsp servlet 编写一个基于 Web 的应用程序 我读过有关sessionFactory getCurrentSession and sessionFactory openSession方法 我知
  • 在 Spring 中为 @Pathvariable 添加类级别验证

    在发布这个问题之前 我已经做了很多研究并尝试了很多可用的解决方案 这是我陷入的棘手情况 我有一个 Spring 控制器 它有多个请求映射 它们都有 PathVariables 控制器如下所示 Controller EnableWebMvc
  • 2^31 次方的 Java 指数错误 [重复]

    这个问题在这里已经有答案了 我正在编写一个java程序来输出2的指数幂 顺便说一句 我不能使用Math pow 但是在 2 31 和 2 32 处我得到了其他东西 另外 我不打算接受负整数 My code class PrintPowers
  • 如何自定义舍入形式

    我的问题可能看起来很简单 但仍然无法得到有效的东西 我需要自定义 Math round 舍入格式或其他格式以使其工作如下 如果数字是 1 6 他应该四舍五入到 1 如果大于或等于 1 7 他应该四舍五入到 2 0 对于所有其他带有 6 的小
  • Android - 存储对ApplicationContext的引用

    我有一个静态 Preferences 类 其中包含一些应用程序首选项和类似的内容 可以在那里存储对 ApplicationContext 的引用吗 我需要该引用 以便我可以在不继承 Activity 的类中获取缓存文件夹和类似内容 你使用的
  • 我们如何使用 thymeleaf 绑定对象列表的列表

    我有一个表单 用户可以在其中添加任意数量的内容表对象这也可以包含他想要的列对象 就像在 SQL 中构建表一样 我尝试了下面的代码 但没有任何效果 并且当我尝试绑定两个列表时 表单不再出现 控制器 ModelAttribute page pu
  • java实现excel价格、收益率函数[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 即使禁用安全性,OAuth 令牌 API 也无法在 Elastic Search 中工作

    我是 Elastic search 新手 使用 Elastic search 版本 7 7 1 我想通过以下方式生成 OAuth 令牌弹性搜索文档 https www elastic co guide en elasticsearch re
  • Spring Data Rest 多对多 POST

    首先 让我解释一下我的用例 这非常简单 有一个用户实体和一个服务实体 我使用 UserService 作为连接实体 连接表 在用户和服务之间建立多对多关联最初 会有一些用户集和一些服务集 用户可以在任何时间点订阅任何服务 在这种情况下 将向
  • Java 中清除嵌套 Map 的好方法

    public class MyCache AbstractMap

随机推荐

  • test is not a function (js正则表达式匹配问题)

    js中正则表达式匹配时 如果使用test函数 就必须不带引号 并且必须是 定义的规则变量 test 要测试的string 定义变量规则不要带引号 会错误的 如果不使用test 使用match则可以带引号 var re 1 9 d 4 10
  • Android 组件逻辑漏洞漫谈

    前言 随着社会越来越重视安全性 各种防御性编程或者漏洞缓解措施逐渐被加到了操作系统中 比如代码签名 指针签名 地址随机化 隔离堆等等 许多常见的内存破坏漏洞在这些缓解措施之下往往很难进行稳定的利用 因此 攻击者们的目光也逐渐更多地投入到逻辑
  • QT4信号连接与QT5的区别

    QT4信号连接与QT5的区别 QT4信号与槽 1 申明槽函数必须增加public slots 2 SIGNAL SLOT 将函数转为字符串 不进行错误检查 connect中信号和槽需要增加SIGNAL 和SLOT 3 槽函数和信号一致 参数
  • 常用的表格正则验证 + 省份选择 JS JQ

    常用的表格正则验证 轮子 let receiverNameReg u4e00 u9fa5 2 6 reg 收货人姓名 let receiverName receiverName val 收货人姓名 let phoneNumberReg d
  • TCP的几个状态 SYN, FIN, ACK, PSH, RST, URG

    2019独角兽企业重金招聘Python工程师标准 gt gt gt TCP的几个状态对于我们分析所起的作用 在TCP层 有个FLAGS字段 这个字段有以下几个标识 SYN FIN ACK PSH RST URG 其中 对于我们日常的分析有用
  • 数据挖掘技术-绘制散点图

    绘制散点图 前置步骤 准备数据guomin npz 下载数据guomin npz到Linux本地的 course DataAnalyze data目录 绘制散点图 绘制2000 2017年各季度的国民生产总值散点图 如代码 41所示 代码
  • 【华为OD机试真题 JAVA】执行时长

    JS版 华为OD机试真题 JS 执行时长 标题 执行时长 时间限制 1秒 内存限制 262144K 语言限制 不限 为了充分发挥GPU算力 需要尽可能多的将任务交给GPU执行 现在有一个任务数组 数组元素表示在这1秒内新增的任务个数且每秒都
  • Python脚本报错AttributeError: ‘module’ object has no attribute’xxx’解决方法

    最近在编写Python脚本过程中遇到一个问题比较奇怪 Python脚本完全正常没问题 但执行总报错 AttributeError module object has no attribute xxx 这其实是 pyc文件存在问题 问题定位
  • #C++矩阵类的实现

    C 矩阵类的实现 环境 Win10 VS2017 最近老师布置一个简单的C 作业 实现一个矩阵类 并且实现矩阵运算 主要实现运算为矩阵的加 减 乘 除以及求行列式 伴随矩阵 代数余子式和逆矩阵等 在参考网上的一些前辈的代码后 写出了这些运算
  • 信号与系统复习题

    选择题 2分 题 1 频谱与时域的关系 时域压缩 频域展宽 时域有限 频域无限 2 填空题 20分 2分 空 1 冲击信号的性质 抽样性 尺度变换性 奇偶性 2 线性时不变的概念 线性 齐次性 输入夸大多少倍 输出扩大多少倍 可加性 相应的
  • HFP协议

    通话专题HFP协议学习总结 一 配置和角色 二 HFP的连接 2 1服务级连接建立 2 1 1 服务发现和RFCOMM的连接 2 1 2 支持的特性交换 2 1 3 codec协商 2 1 4 HF指示器 2 1 5 AG指示器 2 1 6
  • ctfshow 文件上传 web151~170

    目录 web151 web 152 web 153 web 154 web 155 web 156 web 157 159 web 160 web 161 web 162 163 web 164 web 165 web 166 web 16
  • STM32F030C8T6 多通道ADC采集

    void adc init void ADC InitTypeDef ADC InitStructure GPIO InitTypeDef GPIO InitStructure RCC ADCCLKConfig RCC ADCCLK PCL
  • 动态规划算法解决背包问题(Java实现)

    文章收藏的好句子 你在书本上花的任何时间 都会在某一个时刻给你回报 目录 1 动态规划算法的概述 2 背包问题 3 动态规划算法解决背包问题 3 1 不可重复装入商品 3 2 思路分析 1 动态规划算法的概述 1 动态规划算法的思想是 将大
  • Python psycopg2使用SimpleConnectionPool数据库连接池以及execute_batch批量插入数据

    有关快速插入大量数据到数据库的一个比较好的博文如下 Fastest Way to Load Data Into PostgreSQL Using Python 其中文末还有提到几种不同方式的对比 效率十分的震撼 可以看看 1 连接池和批量插
  • MYSQL 安装

    MySQL8安装Installer 图文教程 编程宝库 Windows10 MySQL Installer 安装 编程宝库
  • shell提取字符串中的数字保存到变量中

    1 提取数字到变量 temp echo helloworld20180719 tr cd 0 9 echo temp 输出 20180719 2 重定向到文件 echo helloworld20180719 tr cd 0 9 gt mid
  • 【数据结构与算法】--排序

    目录 一 排序的概念及其运用 二 常见的排序算法 2 2选择排序 2 3 交换排序 2 3 4 1 快速排序优化 一 排序的概念及其运用 1 1 排序的概念 排序 所谓排序 就是使一串记录 按照其中的某个或某些关键字的大小 递增或递减的排列
  • [OpenAirInterface实战-14] :OAI nFAPI VNF/PNV持续集成测试的xml配置文件详解

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 120850348 目录 1 nFAPI
  • 23种设计模式之装饰模式

    装饰模式 一个简陋的房子 它可以让人在里面居住 为人遮风避雨 但如果给它进行装修 那么它的居住环境就更加宜人了 程序中的对象也与房子十分类似 首先有一个相当于 房子 的对象 然后经过不断装饰 不断对其增加功能 它就变成了使用功能更加强大的对