【结构型模式】装饰者模式

news2024/9/30 1:43:32

文章目录

    • 优秀借鉴
    • 1、概述
    • 2、结构
    • 3、实现方式
      • 3.1、案例引入
      • 3.2、实现步骤
      • 3.3、案例实现
    • 4、装饰者模式优缺点
    • 5、结构型模式对比
      • 5.1、装饰者模式和代理模式
      • 5.2、装饰者模式和适配器模式
    • 6、应用场景

优秀借鉴

  1. 装饰模式 — Graphic Design Patterns
  2. 设计模式 | 装饰者模式及典型应用
  3. 黑马程序员Java设计模式详解-装饰者模式概述

1、概述

装饰者模式(Decorator)是一种结构型设计模式,它允许你在不改变对象自身的基础上,动态地给一个对象添加额外的功能。该模式是通过创建一个包装对象来实现的,也就是用一个新的对象来包装真实的对象。这个装饰对象与原始对象拥有相同的接口,因此客户端无需更改代码即可使用装饰后的对象。

2、结构

在装饰者模式中,一般会涉及到下面四种角色:

  1. Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作;

  2. ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法);

  3. Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的;

  4. ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。

uml结构

3、实现方式

3.1、案例引入

在生活中或多或少大家应该都点过奶茶,我们就以奶茶为例,假设“007奶茶店”中有原味奶茶和茉莉奶绿两种奶茶,而配料则是有红糖珍珠和芝士奶盖两种,每种奶茶都能添加不同的配料,且价格不同。

image-20230411202549756

现在要求的是计算用户下单不同奶茶的价格,我们很直观的能够想象到把每种情况都列举出来即可,通过继承实现多种不同的搭配:

image-20230411203820705

但是有个问题不知道大家有没有看出来,通过这种继承的方式,很容易产生类爆炸,种类少还好,一旦组合多起来那将是不可描述的一场类灾难(反正我画上面图的时候就挺累的),这时我们就可以使用上这里说到的装饰者模式来进行优化。

3.2、实现步骤

实现装饰者模式的步骤如下:

  1. 定义一个基础接口或抽象类,作为所有具体组件和装饰者的公共接口;
  2. 创建具体的组件类,实现基础接口或抽象类,并提供基础功能;
  3. 创建一个抽象的装饰者类,它包含一个基础接口或抽象类类型的成员变量,并实现基础接口或抽象类。这个类通常是一个抽象类,因为它的目的是让子类来扩展装饰行为;
  4. 创建具体的装饰者类继承自抽象的装饰者类,重写基础方法并在方法执行前后添加自己的逻辑,还可以增加新的方法;
  5. 在客户端代码中,使用具体的组件对象来声明一个基础接口或抽象类类型的变量,然后将装饰者对象赋值给该变量。由于装饰者对象也实现了基础接口或抽象类,所以可以通过该变量对被装饰对象进行操作。

3.3、案例实现

我们先来分析一下上面的角色担任:

  • 奶茶:对应装饰者模式中的抽象构件,是具体构件抽象装饰类共同父类
  • 原味奶茶和茉莉奶绿:对应装饰者模式中的具体构件,装饰器可给它增加额外的职责
  • 配料:对应装饰者模式中的抽象装饰类,为抽象构件奶茶的子类,用于给具体构件增加职责
  • 红糖珍珠和芝士奶盖:对应装置者模式中的具体装饰类,负责给构件添加新的职责。

案例uml

使用代码通过装饰者模式实现上述场景如下:

首先定义一个奶茶接口(当然,也可以是抽象类):

/**
 * 奶茶抽象类或接口(抽象构件)
 */
public interface MilkTea {
    String getDescription();
    double getPrice();
}

然后实现两种奶茶:

/**
 * 原味奶茶(具体构件)
 */
@Data
public class OriginalMilkTea implements MilkTea {
    private final String description = "原味奶茶";
    private final double price = 10.0;

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double getPrice() {
        return price;
    }
}

/**
 * 茉莉奶绿(具体构件)
 */
@Data
public class JasmineMilkTea implements MilkTea {
    private final String description = "茉莉奶绿";
    private final double price = 12.0;

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double getPrice() {
        return price;
    }
}

然后定义一个抽象的配料类:

/**
 * 配料抽象类(抽象装饰器)
 */
@Data
public abstract class CondimentDecorator implements MilkTea {
    protected MilkTea milkTea;

    public CondimentDecorator(MilkTea milkTea) {
        this.milkTea = milkTea;
    }

    @Override
    public String getDescription() {
        return milkTea.getDescription();
    }

    @Override
    public double getPrice() {
        return milkTea.getPrice();
    }
}

最后实现两个具体的配料类:

/**
 * 红糖珍珠配料(具体装饰器)
 */
@Data
public class BrownSugarPearl extends CondimentDecorator {
    private final String description = "红糖珍珠";
    private final double price = 3.0;

    public BrownSugarPearl(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    public String getDescription() {
        return milkTea.getDescription() + ",加" + description;
    }

    @Override
    public double getPrice() {
        return milkTea.getPrice() + price;
    }
}

/**
 * 芝士奶盖配料(具体装饰器)
 */
@Data
public class CheeseCream extends CondimentDecorator {
    private final String description = "芝士奶盖";
    private final double price = 5.0;

    public CheeseCream(MilkTea milkTea) {
        super(milkTea);
    }

    @Override
    public String getDescription() {
        return milkTea.getDescription() + ",加" + description;
    }

    @Override
    public double getPrice() {
        return milkTea.getPrice() + price;
    }
}

这样我们就可以使用装饰者模式来组合奶茶和配料了:

// 原味奶茶不加任何配料
MilkTea originalMilkTea = new OriginalMilkTea();
System.out.println(originalMilkTea.getDescription() + "价格:" + originalMilkTea.getPrice());

// 茉莉奶绿搭配红糖珍珠
MilkTea jasmineMilkTea = new JasmineMilkTea();
jasmineMilkTea = new BrownSugarPearl(jasmineMilkTea);
System.out.println(jasmineMilkTea.getDescription() + "价格:" + jasmineMilkTea.getPrice());

// 原味奶茶加芝士奶盖
MilkTea originalMilkTeaWithCheese = new OriginalMilkTea();
originalMilkTeaWithCheese = new CheeseCream(originalMilkTeaWithCheese);
System.out.println(originalMilkTeaWithCheese.getDescription() + "价格:" + originalMilkTeaWithCheese.getPrice());

// 茉莉奶绿满配
MilkTea jasmineMilkTea = new JasmineMilkTea();
jasmineMilkTea = new BrownSugarPearl(jasmineMilkTea);
jasmineMilkTea = new CheeseCream(jasmineMilkTea);
System.out.println(jasmineMilkTea.getDescription() + "价格:" + jasmineMilkTea.getPrice());

输出结果:

原味奶茶价格:10.0
茉莉奶绿,加红糖珍珠价格:15.0
原味奶茶,加芝士奶盖价格:15.0
茉莉奶绿,加红糖珍珠,加芝士奶盖价格:20.0

其中第一杯奶茶没有添加任何配料,第二杯奶茶添加了红糖珍珠配料,第三杯奶茶添加了芝士奶盖配料,第四杯则是满配两个配料都添加了。

4、装饰者模式优缺点

装饰者模式是一种结构型设计模式,其主要优点有:

  1. 动态扩展功能:装饰者模式可以在运行时动态地添加、删除和修改对象的功能,从而实现对对象的动态扩展,避免了使用继承带来的静态局限性;

  2. 单一职责原则:装饰者模式将一个大类分为多个小类,每个小类只关注自己的功能实现,符合单一职责原则,使得代码更加清晰简洁;

  3. 开放封闭原则:通过装饰者模式,可以在不改变原有代码的情况下,增强、扩展对象的功能,符合开放封闭原则;

  4. 可组合性:装饰者模式中的装饰者可以任意组合,以增强对象的功能,形成不同的组合结果,具有很好的灵活性和可复用性。

缺点包括:

  1. 多层嵌套:如果使用不当,装饰者模式会导致大量的嵌套和复杂度,使得代码难以维护和理解;

  2. 具体组件与装饰者的耦合:装饰者模式需要每个具体装饰者都依赖于一个具体组件,这种依赖关系可能会导致系统中出现大量的具体类,增加了系统的复杂度。

优点缺点
动态扩展功能多层嵌套
单一职责原则具体组件与装饰者的耦合
开放封闭原则
可组合性

5、结构型模式对比

装饰者模式、代理模式和适配器模式都是常用的设计模式,它们之间有些许相似之处,但也存在一些区别。

5.1、装饰者模式和代理模式

装饰者模式和代理模式的联系:

  1. 装饰者模式和代理模式都委托被包装对象进行操作。在代理模式中,代理对象控制着实际对象的访问,并根据需要对其进行更改或增强。而在装饰者模式中,装饰器对象对被装饰的对象进行了装饰,以增强它的功能;

  2. 装饰者模式和代理模式都可以在运行时动态地增强和修改对象的行为。

装饰者模式和代理模式的区别:

  1. 装饰者模式侧重于在不改变已经存在的对象结构的情况下,动态地将责任附加到对象上,以增强其功能;而代理模式则是控制对对象的访问

  2. 装饰者模式所实现的功能一般都是增强性质的,而代理模式则是控制性质的。

5.2、装饰者模式和适配器模式

适配器模式和装饰者模式的联系和区别:

  1. 适配器模式旨在将一个接口转换成另一个接口,以便于不兼容的对象之间进行交互。而装饰者模式和代理模式并不涉及接口转换

  2. 适配器模式和装饰者模式都是结构型模式。适配器模式主要用于解决接口不兼容的问题,而装饰者模式则主要用于为对象增加新的功能

  3. 适配器模式和代理模式都能够控制对对象的访问,但是它们的目的不同。适配器模式关注接口的转换,代理模式关注控制对对象的访问

6、应用场景

装饰者模式主要用于在不改变原有对象的结构和功能的情况下,动态地增加对象的功能。以下是一些使用装饰者模式的常见应用场景:

  1. 动态地添加对象的职责:通过装饰者模式,可以在运行时动态地为一个对象添加新的职责,而不需要修改它的代码或继承它;

  2. 多个小对象进行组合:使用装饰者模式可以将多个小对象组合成一个大对象,并且可以根据需要随意组合这些小对象,以形成不同的组合结果;

  3. 需要扩展现有类的功能而又不能修改其源代码:在一些开源库或第三方库中,由于源代码无法修改,但是又需要对其功能进行扩展,此时装饰者模式可以非常方便地实现这一需求;

  4. 给已有的对象添加新的行为,而且这些行为还能够互相组合:使用装饰者模式,可以很容易地给一个已有的对象添加新的行为,并且这些行为还能够互相组合,以形成更复杂的行为;

  5. 避免继承带来的子类爆炸问题:通过装饰者模式,可以避免使用继承带来的子类爆炸问题,从而使得系统更加灵活、可扩展。

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

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

相关文章

基于图卷积神经网络GCN的二部图链路预测方法实现思路和完整代码【可用于疾病-靶点、miRNA-疾病等相关预测】

本文解决的问题 基本假设:二部图只有邻接矩阵,没有节点特征,并且进行链路预测,有部分链路未知。 如果你有初始节点特征,直接换掉即可 实现思路 这段代码主要是用于构建一个基于图卷积神经网络(GCN&#…

内网域渗透总结(红日靶场一)

一、靶场环境搭建 红日靶场:http://vulnstack.qiyuanxuetang.net/vuln/detail/2/靶机通用密码:hongrisec2019win7win2003win2008 这个搭建靶场的过程是我经过实际操作(踩坑)总结的最简单省事的方式。 首先创建一个仅主机模式的…

使用appuploader工具发布证书和描述性文件教程

使用APPuploader工具发布证书和描述性文件教程 之前用AppCan平台开发了一个应用,平台可以同时生成安卓版和苹果版,想着也把这应用上架到App Store试试,于是找同学借了个苹果开发者账号,但没那么简单,还要用到Mac电脑的…

数据结构之图(最小生成树+最短路径)

基本概念 连通:若a->b存在路径,即为连通 连通图:该图中任意两点均连通,即为连通图 连通分量:下图为无向图,但存在三个连通分量 强连通图:双向的连通图 强连通分量:有向图中的双…

分享一个免梯子智能的问答工具ChatGPT,刷题和学习的好帮手

使用了这个问答工具后,感觉前后端都要被替代了,太强了。 由于本人之前很想体验,但是一直难搞,最近发现了一个免梯子的,重要事情说一遍,免梯子!是我最近发现的最好用,最快的,且不要梯子的,用起来爽,界面也挺好看的,大家快玩儿玩儿。 试了一下写代码,写文案……真棒…

文心一言 VS chatgpt (9)-- 算法导论2.3 7题 3.1 1题

七、描述一个运行时间为O(nlgn)的算法,给定n个整数的集合S和另一个整数,该算法能确定 S中是否存在两个其和刚好为工的元素。 文心一言: 这里是一个运行时间为O(nlgn)的算法,可以用来确定集合 S 中是否存在两个元素,它…

FE_CSS 页面布局之定位

1 为什么需要定位 某个元素可以自由的在一个盒子内移动位置,并且压住其他盒子。 当我们滚动窗口的时候,盒子是固定屏幕某个位置的。 以上效果,标准流或浮动都无法快速实现,此时需要定位来实现。 浮动可以让多个块级盒子一行没有…

第四章 word2vec 的高速化

目录4.1 word2vec 的改进①4.1.1 Embedding 层4.1.2 Embedding 层的实现4.2 word2vec 的改进②4.2.1 中间层之后的计算问题4.2.2 从多分类到二分类4.2.3 sigmoid 函数和交叉熵误差4.2.4 多分类到二分类的实现4.2.5 负采样4.2.6 负采样的采样方法4.2.7 负采样的实现4.3 改进版 w…

组态王与FX5U之间如何快速实现无线通讯?

本方案是基于Modbus RTU协议下实现的1主多从自组网无线通信形式,主站为组态王,从站为两台三菱FX5U PLC。在工厂里,组态王和plc所处位置距离较为分散,重新铺设电缆线工期长,成本高,故采用日系PLC专用无线通讯…

【Halcon 笔记2】参数

一、图形参数 图形参数 Iconic, 包括 image, region, XLD 1.1 image 图像由一个或者多个通道组成,是大小相同的矩阵,包含各种像素类型的灰度值 在图像显示界面,按ctrl健,可以查看当前的像素值 灰度图 一个通道像素点存放在一个…

STM32开发(十四)STM32F103 数据手册 —— 通用定时器 PWN 详解

文章目录主要特点通用定时器内部框图功能描述计数器模式计数器时钟可选择时钟源PWM输入模式STM32F103内部通用定时器包括TIMx (TIM2、 TIM3、 TIM4和TIM5)定时器 主要特点 16位向上、向下、向上/向下自动装载计数器 16位可编程(可以实时修改)预分频器,计数器时钟频…

【MySQL学习】认识MySQL数据库

目录一、什么是数据库二、主流数据库三、MySQL数据库的基本使用3.1 MySQL的安装3.2 MySQL服务器管理3.3 连接MySQL服务器3.4 MySQL服务器,数据库与表之间的关系3.5 使用案例3.6 数据存储四、MySQL架构五、SQL分类六、存储引擎6.1 定义6.2 查看存储引擎6.3 存储引擎对…

检测图中的负循环 | (贝尔曼福特)

我们得到了一个有向图。我们需要计算图形是否有负循环。负循环是循环的总和变为负的循环。 在图形的各种应用中都可以找到负权重。例如,如果我们沿着这条路走,我们可能会得到一些好处,而不是为一条路付出代价。 例子:

基于html+css的图片展示13

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

震惊!竟然有人如此解释关键字中的static

🤩:大家好,我是paperjie,感谢你阅读本文,欢迎一建三连哦。 🥰:这里是C专栏,笔者用重金(时间和精力)打造,基础知识一网打尽,希望可以帮到读者们哦。 &#x1f…

工作中使用即时通讯软件有什么好处?

以前,即时通讯被认定为是一个专供个人使用的通信工具,即时消息软件不仅用于简化通信和快速响应,而且还用于文件共享和信息更新,它可帮助公司中的员工进行沟通、满足需求并实现目标。在即时通讯的帮助下,员工无需离开办…

交互式shell脚本编程2

当你在终端环境下安装新的软件时,你可以经常看到信息对话框弹出,需要你的输入,比如:RHEL/CentOS自带的setup,对话框的类型有密码箱、检查表、菜单等等。他们可以引导你以一种直观的方式输入必要的信息,使用…

3d可视化精炼数字工厂互动大屏展示提高企业竞争力

随着各种新兴技术的不断崛起和进步,结合云计算、5G通信、物联网等技术突破数据孤岛,加速炼钢厂整个行业的转型升级已成为行业的大趋势。 传统的维修场景中,一线员工的双手难以得到解放,一线工作数据难以收集、保存、输出。一辆汽车…

leetcode刷题(4)

各位朋友们,大家好。这两天我将为大家分享我在学习栈的过程中遇到的题目,我们一起来看看。 文章目录逆波兰表达式求值题目要求用例输入提示做题思路代码实现c语言实现代码Java语言实现代码有效的括号Java代码实现逆波兰表达式求值 leetcode之逆波兰表达…

Python基于机器学习实现的股票价格预测、股票预测源码+数据集,机器学习大作业

Feature与预测目标的选取 选择的feature: 开盘价最高成交价最低成交价成交量 选择的预测目标: 收盘价 因为股票价格的影响因素太多,通过k线数据预测未来的价格变化基本不可行,只有当天之内的数据还有一定的关联,故feature与target都选择的…