瑞_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();
    }

    @Override
    public 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();
    }

    @Override
    public 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 {
 
   @Override
   public void draw() {
      System.out.println("Shape: Rectangle");
   }
}
Circle.java
public class Circle implements Shape {
 
   @Override
   public 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);     
   }
 
   @Override
   public 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 border
	Shape: Circle
	
	Circle of red border
	Shape: Circle
	Border Color: Red
	
	Rectangle of red border
	Shape: Rectangle
	Border 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种设计模式_代理模式》

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



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

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


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java日志技术

概况 把程序运行的信息,记录到文件中,方便程序员定位bug,并了解程序的执行情况等 什么是日志 好比生活中的日记,可以记录你生活中的点点滴滴程序中的日志,通常就是一个文件,里面记录的是程序运行过程中的…

第四十回 宋江智取无为军 张顺活捉黄文炳-使用python集合计算人员变动

白龙庙聚会,梁上好汉人多势众,听说江州城里军队追赶过来了,大家一起出去迎敌。李逵一马当先杀入人群,花荣一箭射倒领头的马军,其它马军掉头就走,把自己的步兵冲倒了一半。大家一直杀到江州城边,…

Android | ArcGIS入门

一、概述 ArcGIS是由Esri开发的地理信息系统(GIS)软件。它用于制图、空间分析和数据可视化。ArcGIS允许用户以各种格式创建、管理、分析和共享地理信息。它通常用于城市规划、环境管理和应急响应等领域。该软件包括一系列工具,用于创建地图、…

KTV点歌系统vue+springboot音乐歌曲播放器系统

目前现有的KTV点歌系统对于用户而言其在线点歌流程仍然过于繁琐,对于歌曲而言其系统安全性并不能保障。同时整套系统所使用的技术相对较为落后,界面不能动态化展示。相比较于其它同类型网站而言不能体现技术先进性。 1.2 项目目标 KTV点歌系统的后台开发…

C语言调试

目录 一.Debug和Release介绍 二.Windows环境调试介绍 三.窗口介绍 (1)自动窗口和局部变量窗口 (2)监视窗口 (3)调用堆栈 (4)查看汇编信息 (5)查看寄存…

Linux笔记之LD_LIBRARY_PATH详解

Linux笔记之LD_LIBRARY_PATH详解 文章目录 Linux笔记之LD_LIBRARY_PATH详解1.常见使用命令来设置动态链接库路径2.LD_LIBRARY_PATH详解设置 LD_LIBRARY_PATH举例注意事项 3.替代方案使用标准路径编译时指定链接路径优先使用 rpath 还是 runpath?注意事项 1.常见使用…

四信AI智能识别及计量监测设备,助力入河入海排污口规范化建设

随着城市化和工业化的快速发展,污水排放已成为主要的环境问题之一。2022年,国务院办公厅发布《关于加强入河入海排污口监督管理工作的实施意见》,提出“加强科技研发,开展各类遥感监测、水面航测、水下探测、管线排查等实用技术和…

Curator基本使用

文章目录 1. 基本操作1.1 建立连接1.2 创建结点1.3 查询结点查询数据查询子结点查看结点信息 1.4 修改结点普通修改带乐观锁的修改 1.5 删除删除单个结点删除带子结点的结点必须成功的删除带回调函数的删除 2. 监听器事件2.1 NodeCache单一结点连续监听2.2 PathChildrenCache监…

GEE入门篇|遥感专业术语(实践操作2):空间分辨率(Spatial Resolution)

目录 空间分辨率(Spatial Resolution) 1.MODIS(搭载在Aqua 和 Terra 卫星上) 2. TM(搭载在早期LandSat卫星上) 3.MSI(搭载在在Sentinel-2 卫星上) 4.NAIP 空间分辨率&#xff0…

基于qt的图书管理系统----03核心界面设计

参考b站:视频连接 源码github:github 目录 1 添加软件图标2 打包程序3 三个管理界面设计4 代码编写4.1 加载界面4.2 点击按钮切换界面4.3 组团添加样式4.4 搭建表头4.5 表格相关操作 从别人那里下载的项目会有这个文件,里边是别人配置的路径…

ETL:数据转换与集成的关键过程

ETL:数据转换与集成的关键过程 在现代数据驱动的世界中,有效地管理和处理数据对于企业的成功至关重要。ETL(提取、转换、加载)是一种关键的数据处理过程,有助于将数据从源系统提取、清洗、转换并加载到目标系统中&…

大蟒蛇(Python)笔记(总结,摘要,概括)——第9章 类

目录 9.1 创建和使用类 9.1.1 创建Dog类 9.1.2 根据类创建实例 9.2 使用类和实例 9.2.1 Car类 9.2.2 给属性指定默认值 9.2.3 修改属性的值 9.3 继承 9.3.1 子类的_init_()方法 9.3.2 给子类定义属性和方法 9.3.3 重写父类中的方法 9.3.4 将实例用作属性 9.3.5 模拟实物 9.…

Maven setting.xml 配置

目的:可以把我们书写的jar包发布到maven私有仓库,简称私仓 1. 打开云效 2.点击 非生产库-snapshot mave release仓库与snapshot仓库区别? 在软件开发中,"Maven release 仓库"和"Maven snapshot 仓库"是两种…

google浏览器chrome无法访问localhost等本地虚拟域名的解决方法

场景一: 谷歌浏览器访问出现:forbbiden 403 问题,或者直接跳转到正式域名(非本地虚拟域名) 访问本地的虚拟域名http://www.hd.com/phpinfo.php?p1发生了302 条状 火狐浏览器正常访问; 解决方法: 方法1:在谷歌浏览器…

8 buuctf解题

[BJDCTF2020]just_a_rar 1 下载,得到 发现有加密 使用ARCHPR设置四位数掩码爆破 得到口令2016,解压得到图片,flag在图片exif中 在备注里面看见了flag [HBNIS2018]excel破解 1 下载下来是attachment.xls 修改后缀为rar 使用010 Editor打开&a…

5G网络建设 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 200分 题解: Java / Python / C 题目描述 现需要在某城市进行5G网络建设,已经选取N个地点设置5G基站,编号固定为1到N, 接下来需要各个基站之间使用光纤进行连接以确保基…

基于Docker和Springboot两种方式安装与部署Camunda流程引擎

文章目录 前言1、Docker安装1.1、拉取Camunda BPM镜像1.2、编写docker启动camunda容器脚本1.3、docker启动脚本1.4、访问验证 2、SpringBoot启动2.1、下载地址2.2、创建SpringBoot项目并配置基础信息2.3、下载SpringBoot项目并在idea中打开2.4、pom修改2.5、application.yml配置…

P1927 防护伞

题目传送门:P1927 防护伞 作业出了这道题,写一篇题解纪念一下。 这道题可以简化为“先枚举所有点,然后把这些点到另外点距离的最大距离和其他点比较,求出最小距离”。 这样说可能也听不懂,还可以再简化: …

深度学习环境配置常见指令

首先打开anaconda prompt,激活对应虚拟环境。 导入torch并获取对应版本 import torch torch.__version__导入torchvision并获取对应版本 import torchvision torchvision.__version__ 检查cuda是否可用 torch.cuda.is_available() 获取CUDA设备数 torch.cuda.…

85、字符串操作的优化

上一节介绍了在模型的推理优化过程中,动态内存申请会带来额外的性能损失。 Python 语言在性能上之所以没有c++高效,有一部分原因就在于Python语言将内存的动态管理过程给封装起来了,我们作为 Python 语言的使用者是看不到这个过程的。 这一点有点类似于 c++ 标准库中的一些…