第八章结构型模式—装饰者模式

news2024/10/6 5:53:36

文章目录

  • 装饰者模式
    • 解决的问题
    • 概念
    • 结构
  • 案例
    • 使用装配者进行改进
  • 使用场景
    • JDK源码分析
  • 静态代理和装饰者的区别

结构型模式描述如何将类或对象按某种布局组成更大的结构,有以下两种:

  • 类结构型模式:采用继承机制来组织接口和类。
  • 对象结构型模式:釆用组合或聚合来组合对象。

由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。

结构型模式分为以下 7 种:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

装饰者模式

解决的问题

img

  • 假如我们有一个初代的机器人,他具有对话,唱歌,放音乐的功能,如果我们想要一个新的功能,拖地和跳舞
  • 第一种方式就是我们用第二代机器人,第二代机器人利用继承了第一代机器的基础上进行扩展,如果我们想要新的功能,就需要第三代机器人(因为要满足开放闭合的原则)
    • 由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
    • 我们要引入一个有功能为飞行的机器人就需要重新定义一个类,如果有一天又想要一个游泳的功能,又得重新定义一个类,哪天不想要飞行功能了,又得重新定义一个类来只实现游泳
  • 第二种模式就用我们的装饰器模式,我们类似在初代机器人上套一个盒子,然后在盒子上进行功能的扩展 这样就可以扩展一个类的功能。 动态增加功能,动态撤销。
    • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

概念

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

结构

装饰者(Decorator)模式中的角色:

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

案例

在这里插入图片描述

先来看一个快餐店的例子:快餐店有炒面、炒饭等快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。下面是使用继承方式的类图:

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

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

使用装配者进行改进

采用使用装饰者模式对快餐店案例进行改进,类图如下:

image-20230504141909725

抽象构件角色—快餐类

@Data
public abstract class FastFood {
    private float price;//价格
    private String desc;//描述

    public FastFood(float price, String desc) {
        this.price = price;
        this.desc = desc;
    }
    public abstract float cost();
}

具体构建角色—炒饭 炒面

public class FireRice extends FastFood{

    public FireRice() {
        super(10,"炒饭");
    }

    @Override
    public float cost() {
        return getPrice();
    }
}
public class FireNoodles extends FastFood{
    public FireNoodles() {
        super(12, "炒面");
    }

    @Override
    public float cost() {
        return getPrice();
    }
}

抽象装饰者角色:配料类

public abstract class Garnish extends FastFood{
    //声明快餐类的变量
    private FastFood fastFood;

    public Garnish(FastFood fastFood,float price,String desc){
        super(price,desc);
        this.fastFood=fastFood;
    }

}
  • 为什么我们的配料类要求继承我们的FastFood? 有什么意义呢?等后面的测试时的分析

具体的装饰者角色 鸡蛋和培根

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

测试

public class Client {
    public static void main(String[] args) {
        //  点一份炒饭
        FastFood fastFood=new FireNoodles();
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
        //加一个鸡蛋
        fastFood=new Egg(fastFood);
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
        //加一个培根
        fastFood=new Bacon(fastFood);
        System.out.println(fastFood.getDesc() + "  " + fastFood.cost() + "元");
    }
}
//炒面  12.0元
//鸡蛋炒面  13.0元
//培根鸡蛋炒面  15.0元
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
    • 就像一开始举例子的机器人,我们第二代的机器人不是在我们的第一代机器的基础上进行功能扩展,我们类似在初代机器人上套一个盒子,然后在盒子上进行功能的扩展 去掉箱子就取消对应的拓展
  • fastFood=new Egg(fastFood) 我们用的是new Egg,最后还是用fastFood去接收,这也是我们为什么要让配料去继承我们的FastFood,不然在这我们只能用配料类去接收返回的对象,而不是用传进去的fastFood对象
    • 我们本来的意思是给我们的炒面加一个鸡蛋。所以返回的应该还是我们的炒面,如果不实现继承,那么我们加个鸡蛋,最后返回了却是一个配料(鸡蛋),这是不符合我们的意图的

使用场景

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

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

    • 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。

    • 第二类是因为类定义不能继承(如 final 类)。

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

  • 当对象的功能要求可以动态地添加,也可以再动态地撤销时。

JDK源码分析

JDK源码解析
IO流中的包装类使用到了装饰者模式:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。

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

// 创建FileWriter对象
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
// 创建BufferedWriter对象
BufferedWriter bw = new BufferedWriter(fw);
// 写数据
bw.write("hello Buffered");
bw.close();

使用起来感觉确实像是装饰者模式,接下来看它们的结构

在这里插入图片描述

  • InputStreamWriter是抽象构件 FileWriter是具体构件
  • BufferWriter是具体装饰角色

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

静态代理和装饰者的区别

相同点:

  • 都要实现与目标类相同的业务接口

  • 在两个类中都要声明目标对象

  • 都可以在不修改目标类的前提下增强目标方法

不同点:

  • 目的不同

    • 装饰者是为了增强目标对象

      • 静态代理是为了保护和隐藏目标对象
  • 装饰者可以迭代增强,代理只能增强一次

    • 比如我们上面的例子,我们的炒饭可以加鸡蛋,然后再加一个培根
    • 而我们的代理,比如我们的日志功能,我们想要给某个类添加日志功能,我们直接进行调用代理对象进行功能增强
  • 获取目标对象构建的地方不同

    • 装饰者是由外界传递进来,可以通过构造方法传递
      • 我们的装饰着中的具体构建对象是由外部传入到我们的装配者中
    • 静态代理是在代理类内部创建,以此来隐藏目标对象
      • 我们的静态代理中的真实主题类对象是在我们的静态类内部中创建的。而不是由外部传入的

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

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

相关文章

【Linux】volatile | SIGCHLD | 多线程概念

文章目录 1. volatile编译器优化 2.SIGCHLD信号验证SIGCHLD的存在 3. 多线程多线程概念理解概念什么是多线程调度成本低局部性原理 什么叫做进程 1. volatile 在vscode中,创建signal.c文件 故意在while中没有写代码块,让编译器认为在main中,…

爬虫+可视化 | 动态展示2020东京奥运会奖牌世界分布

文章目录 前言1. 导入模块2. 数据爬取3. 地图展示 3.1 2020东京奥运会奖牌数世界分布3.2 2020东京奥运会金牌世界分布3.3 2020东京奥运会金、银、铜世界分布 前言 2020东京奥运会已落下帷幕,中国军团共获得88枚奖牌,其中38枚金牌、32枚银牌、18枚铜牌…

基于RV1126平台检测模型全流程部署(附工程)

基于RV1126平台检测模型全流程部署 模型训练ONNX导出ONNX模型简化Python部署C部署 本工程地址:https://github.com/liuyuan000/Rv1126_YOLOv5-Lite 模型训练 这次选用的是方便部署的YOLOv5 Lite模型,是一种更轻更快易于部署的YOLOv5,主要摘…

嵌入式通信协议【Modbus】modbus RTU的帧格式

modbus的帧格式 设备地址功能代码数据格式CRC校验LCRC校验H8bit8bitN*8bit8bit8bit 1 主机对从机单个寄存器写数据操作(0x06) 从机地址功能代码数据格式(数据地址)数据格式(数据)CRC校验LCRC校验H010600…

动态规划:万变不离其宗,带你吃透股票系列问题

前言: 对于买卖股票问题而言,最关键的是我们对问题的处理方式(对于每一天而言,我们应该描述当天买入卖出还是只描述每天股票的只有或者不持有的状态呢?)我们应该描述每天股票是否持有的状态,因…

中科院发布多模态 ChatGPT,图片、语言、视频都可以 Chat ?中文多模态大模型力作

作者 | 小戏、ZenMoore 在 GPT-4 的发布报道上,GPT-4 的多模态能力让人印象深刻,它可以理解图片内容给出图片描述,甚至能在图片内容的基础上理解其中的隐喻或推断下一时刻的发展。无疑,面向所谓的 AGI(通用人工智能&am…

数据结构初阶(1)(一些学习数据结构所需掌握的先导知识:包装类、装箱与拆箱、泛型、List简介)

包装类 基本数据类型和包装类是Java中处理数据的两种不同方式。 基本数据类型(Primitive Types): Java的基本数据类型是直接存储数据的原始类型,包括以下8种类型: byte:1字节,用于表示整数 …

IEEE编写LaTeX时在作者后添加ORCID标志及链接(简单方案,一行代码)

IEEE的一些论文,如Trans系列惯例是要在作者后添加ORCID标志及链接,但是其How to里面没有相关latex代码案例。 1. 可以用但复杂的方案 CSDN中不少博主也给出了挺漂亮但是比较复杂的方案,如这个的一大串: \documentclass[letters…

Linux文本之awk编译器

一、awk介绍 1)awk概述 AWK 是一种用于处理文本的编程语言工具。AWK 在很多方面类似于 shell 编程语言,尽管 AWK 具有完全属于其本身的语法。它的设计思想来源于 SNOBOL4 、sed 、Marc Rochkind设计的有效性语言、语言工具 yacc 和 lex ,当…

尚硅谷大数据技术NiFi教程-笔记02【NiFi(使用案例,同步文件、离线同步mysql数据到hdfs、实时监控kafka数据到hdfs)】

尚硅谷大数据技术-教程学习路线-笔记汇总表【课程资料下载】 视频地址:尚硅谷大数据NiFi教程(从部署到开发)_哔哩哔哩_bilibili 尚硅谷大数据技术NiFi教程-笔记01【NiFi(基本概念、安装、使用)】尚硅谷大数据技术NiFi教…

探索古文明,玛雅文明衰落的原因

说起玛雅文明,大家在各种小说或者电影中或多或少的都有听说过,那么这个文明到底是怎么一回事呢?今天老铁就带大家好好的了解下。 玛雅文明存在的时间大致是在公元前2000年至公元1500年之间,这个文明见证了中美洲地区的一段辉煌的…

Cefsharp109.1.110(winfrom)最新支持H264-MP3-MP4功能体验,导出pdf和下载方法有变调整

最新版的支持H264版本(109.1.11,109.1.18)5154分支,也是win7/8/8.1最后一个支持版本 此贴仅分项版本变化和注意事项,本篇文章不提供dll编译文件,有需要单独联系,仅供学习参考 109版本体验测试(音频和视频功能),版本较100.0.230变化提醒及注意变更的内容。 上视频支…

C++每日一练:难题-大数加法

文章目录 前言一、题目二、代码及思路总结 前言 这题好像是指定了C,那就用C来做嘛,确实在C/C中一不小心就超出范围了,说实在的,C这个语言有时候真的很让人无语。很显然这是要用字符串来计算了。这题坑比较多,笔者这也…

数据库使用自增ID好还是UUID好?为什么?

数据库使用自增ID好还是UUID好?为什么? 答: 自增ID 优点: 数字类型,占用空间小数据库自动增量排序,对检索有利,读写速度快(聚簇索引的优势)系统编码过程中&#xff0…

知识推理学习笔记

OWL本体语言 基于RDF语法,最规范,最严谨,表达能力最强 一 语法 三元组 二 逻辑基础 描述逻辑:基于对象的知识表示的形式化,是一阶谓词逻辑的一个可判定子集 三 描述逻辑系统 一个描述逻辑包含4个基本组成部分 …

【Python数据存储】零基础也能轻松掌握的学习路线与参考资料

Python是一种高级编程语言,被广泛应用于数据科学中。数据存储是数据科学中至关重要的一环,因为人们需要将收集到的数据保存在一些地方。Python中的数据存储有很多种,因此在学习过程中需要明确自己的需求,掌握不同数据存储方式的优…

深入理解 python 虚拟机:多继承与 mro

深入理解 python 虚拟机:多继承与 mro 在本篇文章当中将主要给大家介绍 python 当中的多继承和 mro,通过介绍在多继承当中存在的问题就能够理解在 cpython 当中引入 c3 算法的原因了,从而能够帮助大家更好的了理解 mro 。 python 继承的问题…

【Linux】shell编程之—函数

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、概述二、函数的查看和删除1.查看 declare2.删除 declare 三、函数的返回值1.return 返回值2.echo 返回值 四、函数的参数传入与变量范围五、函数的应用1.阶乘2.…

十五、Gateway网关

目录 Zuul网关和gateway网关的区别: Gateway路由配置 1、新建服务网关项目,并在项目pom文件中引入gateway网关依赖 2、在application.yml配置gateway 3、如果不用配置的方式配置gateway路由,还可以通过代码的形式配置 4、启动网关服务&…

达梦:创建用户并授予用户只读权限

需求描述: 1.想创建一个用户,这用户能访问其他3个用户的资源,权限是只读,这种创用户的sql怎么写? 2.怎么修改用户密码呢? 环境: 通用机 一、创建用户并授权 1.创建业务用户步骤 step1:创建用户使…