瑞_23种设计模式_装饰者模式

文章目录

    • 1 装饰者模式(Decorator Pattern)
      • 1.1 介绍
      • 1.2 概述
      • 1.3 装饰者模式的结构
    • 2 案例一
      • 2.1 需求
      • 2.2 代码实现
    • 3 案例二
      • 3.1 需求
      • 3.2 代码实现
    • 4 JDK源码解析
    • 5 总结
      • 5.1 装饰者模式的优缺点
      • 5.2 装饰者模式的使用场景
      • 5.3 装饰者模式 VS 代理模式

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的装饰者模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《后续更新》
  桥接模式:《后续更新》
  外观模式:《后续更新》
  组合模式:《后续更新》
  享元模式:《后续更新》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《后续更新》
  策略模式:《后续更新》
  命令模式:《后续更新》
 职责链模式:《后续更新》
  状态模式:《后续更新》
 观察者模式:《后续更新》
 中介者模式:《后续更新》
 迭代器模式:《后续更新》
 访问者模式:《后续更新》
 备忘录模式:《后续更新》
 解释器模式:《后续更新》

在这里插入图片描述

1 装饰者模式(Decorator Pattern)

  装饰者模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

瑞:结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性

  装饰者模式通过将对象包装在装饰器类中,以便动态地修改其行为。

  这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

1.1 介绍

  • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

  • 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

  • 何时使用:在不想增加很多子类的情况下扩展类。

  • 如何解决:将具体功能职责划分,同时继承装饰者模式。

  • 关键代码
      1️⃣ Component 类充当抽象角色,不应该具体实现。
      2️⃣ 修饰类引用和继承 Component 类,具体扩展类重写父类方法。

  • 应用实例
      1️⃣ 孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。
      2️⃣ 不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。

  • 优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

  • 缺点:多层装饰比较复杂。

  • 使用场景
      1️⃣ 扩展一个类的功能。
      2️⃣ 动态增加功能,动态撤销。

  • 注意事项:可代替继承。

1.2 概述

  定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。

  先看一个快餐店的例子

  快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。

  该需求类图如下(未使用装饰者模式设计):
在这里插入图片描述

  使用继承的方式存在的问题:

  • 扩展性不好
      如果要再加一种配料(火腿肠),我们就会发现需要给 FriedRice 和 FriedNoodles 分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。

  • 产生过多的子类

1.3 装饰者模式的结构

  • 装饰(Decorator)模式中的角色:
      1️⃣ 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
      2️⃣ 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
      3️⃣抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
      4️⃣ 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。



2 案例一

快餐店

2.1 需求

  使用装饰者模式对快餐店案例(点我跳转)进行改进,体会装饰者模式的精髓。

  使用装饰者模式设计后的类图如下:

在这里插入图片描述

2.2 代码实现

快餐类(抽象类)
/*** 快餐类(抽象构件角色)** @author LiaoYuXing-Ray**/
public abstract class FastFood {// 价格private float price;// 描述private String desc;public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public FastFood(float price, String desc) {this.price = price;this.desc = desc;}public FastFood() {}// 计算价格public abstract float cost();
}
炒饭(类)
/*** 炒饭(具体构件角色)** @author LiaoYuXing-Ray**/
public class FriedRice extends FastFood {public FriedRice() {super(10, "炒饭");}public float cost() {return getPrice();}
}
炒面(类)
/*** 炒面(具体的构件角色)** @author LiaoYuXing-Ray**/
public class FriedNoodles extends FastFood {public FriedNoodles() {super(12,"炒面");}public float cost() {return getPrice();}
}
装饰者类(抽象类)
/*** 装饰者类(抽象装饰者角色)** @author LiaoYuXing-Ray**/
public abstract class Garnish extends FastFood {// 声明快餐类的变量private FastFood fastFood;public FastFood getFastFood() {return fastFood;}public void setFastFood(FastFood fastFood) {this.fastFood = fastFood;}public Garnish(FastFood fastFood,float price, String desc) {super(price, desc);this.fastFood = fastFood;}
}
鸡蛋类(类)
/*** 鸡蛋类(具体的装饰者角色)** @author LiaoYuXing-Ray**/
public class Egg extends Garnish {public Egg(FastFood fastFood) {super(fastFood,1,"鸡蛋");}public float cost() {//计算价格return getPrice() + getFastFood().cost();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}
培根类(类)

/*** 培根类(具体的装饰者角色)** @author LiaoYuXing-Ray**/
public class Bacon extends Garnish {public Bacon(FastFood fastFood) {super(fastFood,2,"培根");}public float cost() {//计算价格return getPrice() + getFastFood().cost();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}
测试类
/*** 测试类** @author LiaoYuXing-Ray**/
public class Client {public static void main(String[] args) {// 点一份炒饭FastFood food = new FriedRice();System.out.println(food.getDesc() + "  " + food.cost() + "元");System.out.println("===============");// 在上面的炒饭中加一个鸡蛋food = new Egg(food);System.out.println(food.getDesc() + "  " + food.cost() + "元");System.out.println("================");// 再加一个鸡蛋food = new Egg(food);System.out.println(food.getDesc() + "  " + food.cost() + "元");System.out.println("================");food = new Bacon(food);System.out.println(food.getDesc() + "  " + food.cost() + "元");}
}

  代码运行结果如下:

	炒饭  10.0元===============鸡蛋炒饭  11.0元================鸡蛋鸡蛋炒饭  12.0元================培根鸡蛋鸡蛋炒饭  14.0元

好处:

  • 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。



3 案例二

本案例为菜鸟教程中的案例

3.1 需求

  把一个形状装饰上不同的颜色,同时又不改变形状类

  创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape 对象作为它的实例变量。

  RedShapeDecorator 是实现了 ShapeDecorator 的实体类。

  DecoratorPatternDemo 类使用 RedShapeDecorator 来装饰 Shape 对象。

在这里插入图片描述

3.2 代码实现


步骤1

  创建一个接口。

Shape.java
public interface Shape {void draw();
}

步骤2

  创建实现接口的实体类。

Rectangle.java
public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("Shape: Rectangle");}
}
Circle.java
public class Circle implements Shape {@Overridepublic void draw() {System.out.println("Shape: Circle");}
}

步骤3

  创建实现了 Shape 接口的抽象装饰类。

ShapeDecorator.java
public abstract class ShapeDecorator implements Shape {protected Shape decoratedShape;public ShapeDecorator(Shape decoratedShape){this.decoratedShape = decoratedShape;}public void draw(){decoratedShape.draw();}  
}

步骤4

  创建扩展了 ShapeDecorator 类的实体装饰类。

RedShapeDecorator.java
public class RedShapeDecorator extends ShapeDecorator {public RedShapeDecorator(Shape decoratedShape) {super(decoratedShape);     }@Overridepublic void draw() {decoratedShape.draw();         setRedBorder(decoratedShape);}private void setRedBorder(Shape decoratedShape){System.out.println("Border Color: Red");}
}

步骤5

  使用 RedShapeDecorator 来装饰 Shape 对象。

DecoratorPatternDemo.java
public class DecoratorPatternDemo {public static void main(String[] args) {Shape circle = new Circle();ShapeDecorator redCircle = new RedShapeDecorator(new Circle());ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle());//Shape redCircle = new RedShapeDecorator(new Circle());//Shape redRectangle = new RedShapeDecorator(new Rectangle());System.out.println("Circle with normal border");circle.draw();System.out.println("\nCircle of red border");redCircle.draw();System.out.println("\nRectangle of red border");redRectangle.draw();}
}

步骤6

执行程序,输出结果:

	Circle with normal borderShape: CircleCircle of red borderShape: CircleBorder Color: RedRectangle of red borderShape: RectangleBorder Color: Red



4 JDK源码解析

  IO流中的包装类使用到了装饰者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

  我们以BufferedWriter举例来说明,先看看如何使用BufferedWriter

public class RayTest {public static void main(String[] args) throws Exception{// 获取当前文件的绝对路径String absolutePath = System.getProperty("user.dir");String filePath = absolutePath + File.separator + "test.txt";// 创建BufferedWriter对象// 创建FileWriter对象FileWriter fw = new FileWriter(filePath);BufferedWriter bw = new BufferedWriter(fw);// 写数据bw.write("hello Buffered");bw.close();}
}

  分析它们的结构,类图如下,使用了装饰者模式的设计思维

在这里插入图片描述

BufferedWriter使用装饰者模式对Writer子实现类进行了增强,添加了缓冲区,提高了写数据的效率。




5 总结

5.1 装饰者模式的优缺点

优点
  1️⃣ 组合使用:饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,通过使用不同的装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。这意味着可以使用多个装饰类来装饰同一个对象,从而得到功能更为强大的对象。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任
  2️⃣ 动态扩展:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
  3️⃣ 符合开闭原则:装饰者模式允许在不修改原有代码的情况下增加新的功能,这符合开闭原则,即“软件实体(类、模块、函数等等)应当是可扩展,而不可修改的”。
  4️⃣ 易于使用:装饰者模式易于理解和使用,因为它与日常生活中的装饰概念相似。

缺点
  1️⃣ 产生大量小对象:由于装饰者模式是通过创建大量的小对象来实现功能的扩展,这可能会增加系统的内存开销和垃圾回收的压力。
  2️⃣ 可能导致过度设计:如果过度使用装饰者模式,可能会导致系统中有过多的装饰类和具体的构件类,从而使代码变得复杂和难以维护。
  3️⃣ 可能引入额外的性能开销:由于装饰者模式需要在运行时动态地组合对象,这可能会引入额外的性能开销,特别是在需要多层装饰的情况下。

5.2 装饰者模式的使用场景

  • 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。

    不能采用继承的情况主要有两类:

    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
    • 第二类是因为类定义不能继承(如final类)
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。在面向服务的架构(SOA)中,可以使用装饰者模式来组合不同的服务,以创建具有复合功能的新服务。这允许服务提供者灵活地根据客户需求来定制服务

5.3 装饰者模式 VS 代理模式

代理模式可以参考:《瑞_23种设计模式_代理模式》

  • 相同点:
    • 都要实现与目标类相同的业务接口
    • 在两个类中都要声明目标对象
    • 都可以在不修改目标类的前提下增强目标方法
  • 不同点:
    • 目的不同
        装饰者是为了增强目标对象
        静态代理是为了保护和隐藏目标对象
    • 获取目标对象构建的地方不同
        装饰者是由外界传递进来,可以通过构造方法传递
        静态代理是在代理类内部创建,以此来隐藏目标对象



本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


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

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

相关文章

dpdk环境搭建和工作原理

文章目录 1、DPDK环境搭建1.1、环境搭建1.2、编译DPDK 2、DPDK工作原理 1、DPDK环境搭建 1.1、环境搭建 工具准备:VMware、ubuntu16.04。 (1)VMware添加两个网卡。桥接网卡作为 DPDK 运行的网卡,NAT 网卡作为 ssh 连接的网卡。 …

【动态规划】【前缀和】【推荐】2463. 最小移动总距离

作者推荐 【广度优先搜索】【网格】【割点】【 推荐】1263. 推箱子 本文涉及知识点 动态规划汇总 C算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 2463. 最小移动总距离 X 轴上有一些机器人和工厂。给你一个整数数组 robot &#xff0c…

【统计分析数学模型】判别分析(四):机器学习分类算法

【统计分析数学模型】判别分析(四):机器学习分类算法 一、机器学习分类算法1. 交叉验证方法2. 案例数据集3. 数据标准化 二、决策树模型1. 基本原理2. 计算步骤3. R语言实现 三、K最邻近分类1. 基本原理2. K值的选择3. R语言实现 四、支持向量…

5分钟JavaScript快速入门

目录 一.JavaScript基础语法 二.JavaScript的引入方式 三.JavaScript中的数组 四.BOM对象集合 五.DOM对象集合 六.事件监听 使用addEventListener()方法添加事件监听器 使用onX属性直接指定事件处理函数 使用removeEventListener()方法移除事件监听器 一.JavaScript基础…

投屏软件Airserver优惠码来了,使用能减10元(有图有真相)

Airserver是一款非常实用的手机投屏到电脑软件。AirServer for Mac是一款能够通过本地网络将音频、照片、视频以及支持AIrPlay功能的第三方App,从 iOS 设备无线传送到 Mac 电脑的屏幕上,把Mac变成一个AirPlay终端的实用工具。 Airserver中文官网地址&…

【算法与数据结构】回溯算法、贪心算法、动态规划、图论(笔记三)

文章目录 七、回溯算法八、贪心算法九、动态规划9.1 背包问题9.2 01背包9.3 完全背包9.4 多重背包 十、图论10.1 深度优先搜索10.2 广度优先搜索10.3 并查集 最近博主学习了算法与数据结构的一些视频,在这个文章做一些笔记和心得,本篇文章就写了一些基础…

【C++】类和对象---友元,内部类,匿名对象详解

目录 友元 友元函数 友元类 内部类 匿名对象 ⭐友元 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。 友元分为:友元函数和友元类。 ⚡友元函数 先看一个问题&#x…

使用 yarn 的时候,遇到 Error [ERR_REQUIRE_ESM]: require() of ES Module 怎么解决?

晚上回到家,我打开自己的项目,执行: cd HexoPress git pull --rebase yarn install yarn dev拉取在公司 push 的代码,然后更新依赖,最后开始今晚的开发时候,意外发生了,竟然报错了,…

Python流程控制有知道的吗?

流程控制是编程的核心概念之一,Python也不例外。在Python中,程序的流程控制结构主要包括顺序结构、选择结构和循环结构。这些结构让程序员能够更好地组织代码,使其按照特定的逻辑执行。 1.顺序结构 顺序结构是Python中最简单的流程控制结构&…

Android相机调用-libusbCamera【外接摄像头】【USB摄像头】 【多摄像头预览】

有的自定义系统,对于自己外接的USB摄像头,android原生的camera和camera2都无法打开,CameraX也用不了。这时候就要用libusbCamera,这个库可以打开摄像头,还可以多摄像头同时预览。本文主要是同时打开3个USB摄像头的项目…

Spring Boot应用集成Actuator组件以后怎么自定义端点暴露信息

一、 前言 在平时业务开发中,我们往往会在spring Boot项目中集成Actuator组件进行系统监控,虽然Actuator组件暴露的端点信息已经足够丰富了,但是特殊场景下,我们也需要自己暴露端点信息,此时应该怎么操作呢&#xff1…

爬虫知识--03

数据存mysql import requests from bs4 import BeautifulSoup import pymysql# 链接数据库pymysql conn pymysql.connect(userroot,password"JIAJIA",host127.0.0.1,databasecnblogs,port3306, ) cursor conn.cursor() cursor conn.cursor()# 爬数据 res request…

如何解决Nginx启动出现闪退问题?

哈喽,大家好,我是小浪。那么大家首次在启动nginx的时候,绝大部分同学会出现以下情况,就是我们双击nginx.exe文件之后,屏幕闪退一下就没了,然后我们访问localhost:8080提示404. 那么出现这种情况其实是我们…

NestJS入门7:增加异常过滤器

前文参考: NestJS入门1 NestJS入门2:创建模块 NestJS入门3:不同请求方式前后端写法 NestJS入门4:MySQL typeorm 增删改查 NestJS入门5:加入Swagger NestJS入门6:日志中间件 本文代码基于上一篇文章《…

深度学习基础(二)卷积神经网络(CNN)

之前的章节我们初步介绍了深度学习相关基础知识和训练神经网络: 深度学习基础(一)神经网络基本原理-CSDN博客文章浏览阅读924次,点赞13次,收藏19次。在如今的科技浪潮中,神经网络作为人工智能的核心技术之…

关于git子模块实践(一)

背景 在日常项目开发中,随着项目的迭代,不可避免的是主项目会引入到很多三方库,或者自研的一些模块。有一种场景,就是这些模块,是随着开发而进行迭代,且多个项目公用的,这种情况,在…

第3.3章:StarRocks数据导入——Stream Load

一、概述 Stream Load是StarRocks最为核心的导入方式,用户通过发送HTTP请求将本地文件或数据流导入至StarRocks中,其本身不依赖其他组件。 Stream Load支持csv和json两种数据文件格式,适用于数据文件数量较少且单个文件的大小不超过10GB 的场…

v-rep插件

v-rep官网插件汉化教程 官网教程 插件是什么 插件本质上就是遵循一定规范的API编写出来的程序,在v-rep中最终需要编译为动态库。 linux下是libsimXXXX.so; 其中XXXX是插件的名称。 请至少使用4个字符,并且不要使用下划线,因为…

kafka生产者2

1.数据可靠 • 0:生产者发送过来的数据,不需要等数据落盘应答。 风险:leader挂了之后,follower还没有收到消息。。。。 • 1:生产者发送过来的数据,Leader收到数据后应答。 风险:leader应答…

【机器学习】数据清洗——基于Numpy库的方法删除重复点

🎈个人主页:豌豆射手^ 🎉欢迎 👍点赞✍评论⭐收藏 🤗收录专栏:机器学习 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进…