【深入理解设计模式】装饰者设计模式

在这里插入图片描述

装饰者设计模式

装饰者设计模式(Decorator Design Pattern)是一种结构型设计模式,它允许向现有对象添加新功能而不改变其结构。这种模式通常用于需要动态地为对象添加功能或行为的情况,而且这些功能可以独立于对象本身来进行扩展。

定义:

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

装饰(Decourator)模式中的角色:

  • Component(抽象组件角色):定义一个对象接口,可以给这些对象动态地添加职责。

  • ConcreteComponent(具体组件角色):实现Component接口,是被装饰的具体对象,可以动态地为其添加职责。

  • Decorator(抽象装饰者):维持一个指向Component对象的指针,并实现Component接口,这样可以扩展Component对象的功能。

  • ConcreteDecorator(具体装饰者):实现Decorator接口,并扩展Component对象的功能。

示例一:

快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。

/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 快餐类 - 抽象组件者
 */
public abstract class FastFood {

    private float price;
    private String desc;

    public FastFood() {
    }

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

    public abstract float cost();  //获取价格

    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;
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 炒面类 - 具体构件者(待装饰类)
 */
public class FriedNoodles extends FastFood {


    public FriedNoodles(){
        super(12,"炒面");
    }

    @Override
    public float cost() {
        return getPrice();
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 炒饭类 - 具体构件者(待装饰类)
 */
public class FriedRice extends FastFood {

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

    @Override
    public float cost() {
        return getPrice();
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 配料类 - 抽象装饰者 继承或实现抽象组件角色并且在成员位置定义一个抽象组件角色
 */
public abstract class Garnish extends FastFood {

    private FastFood fastFood;

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

    public FastFood getFastFood() {
        return fastFood;
    }

    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 鸡蛋类 - 具体装饰者
 */
public class Egg extends Garnish {


    @Override
    public String getDesc() {
        return super.getDesc()+getFastFood().getDesc();
    }

    public Egg(FastFood fastFood) {
        super(fastFood, 1, "鸡蛋");
    }

    @Override
    public float cost() {
        return getPrice() + getFastFood().cost();
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 抽象装饰者类
 */
public class Bacon extends Garnish {

    public Bacon(FastFood fastFood) {
        super(fastFood, 2, "培根");
    }

    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }

    @Override
    public float cost() {
        return getPrice() + getFastFood().cost();
    }
}
/**
 * @author OldGj 2024/02/25
 * @version v1.0
 * @apiNote 测试类 - 客户端
 */
public class Client {
    public static void main(String[] args) {


        FastFood food = new FriedRice();
        System.out.println(food.getDesc() + "\t" + food.cost() + "元");
        System.out.println("-------------------");
        food = new Egg(food);
        System.out.println(food.getDesc() + "\t" + food.cost() + "元");
        System.out.println("-------------------");
        food = new Bacon(food);
        System.out.println(food.getDesc() + "\t" + food.cost() + "元");

    }
}

在这里插入图片描述

示例二:

考虑一个简单的例子:制作咖啡。在这个例子中,咖啡是Component,具体的咖啡类型(例如:浓缩咖啡、拿铁、摩卡)是ConcreteComponent,添加的调料(例如:牛奶、糖、巧克力)是Decorator,具体的调料种类(例如:奶泡、焦糖)是ConcreteDecorator。

让我们通过一个简单的Java示例来演示装饰者设计模式。我们将创建一个咖啡店的场景,其中有不同种类的咖啡(具体组件),以及不同种类的调料(具体装饰者)。

首先,我们定义一个接口 Coffee 作为组件,表示咖啡的基本功能:

public interface Coffee {
    double cost();
    String getDescription();
}

然后,我们实现具体的咖啡类型,例如 EspressoLatte,它们都实现了 Coffee 接口:

public class Espresso implements Coffee {
    @Override
    public double cost() {
        return 1.99;
    }

    @Override
    public String getDescription() {
        return "Espresso";
    }
}

public class Latte implements Coffee {
    @Override
    public double cost() {
        return 2.49;
    }

    @Override
    public String getDescription() {
        return "Latte";
    }
}

接下来,我们创建一个装饰者 CoffeeDecorator,它也实现了 Coffee 接口,并在构造函数中接受一个 Coffee 对象,用于装饰:

public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost();
    }

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

然后,我们创建具体的调料装饰者,例如 MilkCaramel

public class Milk extends CoffeeDecorator {
    public Milk(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public double cost() {
        return super.cost() + 0.5; // 增加牛奶的价格
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Milk";
    }
}

public class Caramel extends CoffeeDecorator {
    public Caramel(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public double cost() {
        return super.cost() + 0.7; // 增加焦糖的价格
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", Caramel";
    }
}

最后,我们可以在客户端代码中使用这些类来创建不同的咖啡组合:

public class Main {
    public static void main(String[] args) {
        Coffee espresso = new Espresso();
        System.out.println("Cost: " + espresso.cost() + ", Description: " + espresso.getDescription());

        Coffee latteWithMilk = new Milk(new Latte());
        System.out.println("Cost: " + latteWithMilk.cost() + ", Description: " + latteWithMilk.getDescription());

        Coffee latteWithMilkAndCaramel = new Caramel(new Milk(new Latte()));
        System.out.println("Cost: " + latteWithMilkAndCaramel.cost() + ", Description: " + latteWithMilkAndCaramel.getDescription());
    }
}

以上代码将输出:

Cost: 1.99, Description: Espresso
Cost: 2.99, Description: Latte, Milk
Cost: 3.69, Description: Latte, Milk, Caramel

这个示例演示了如何使用装饰者模式来动态地添加调料而不改变咖啡本身的结构,并且可以轻松地组合不同的调料组合。

装饰者模式是一种灵活且强大的设计模式,但它也有一些优点和缺点。

优缺点

优点:
  1. 灵活性:装饰者模式允许动态地为对象添加功能,而无需改变其原始类。这使得我们可以根据需要灵活地组合各种功能,而不会造成类爆炸。

  2. 遵循开放封闭原则:通过装饰者模式,我们可以通过添加新的装饰者来扩展功能,而无需修改现有代码,符合开放封闭原则。

  3. 单一职责原则:每个装饰者类都专注于提供单一的功能,从而遵循了单一职责原则,使得代码更易于维护和理解。

  4. 可重复使用性:由于装饰者模式通过组合的方式提供功能,因此这些装饰者可以被多次重复使用,而不会引入代码重复。

缺点:
  1. 复杂性:装饰者模式可能会导致类的数量增加,特别是在有多个装饰者时,这可能会增加代码的复杂性,使得理解和维护变得困难。

  2. 顺序依赖性:装饰者的添加顺序可能会影响最终的行为,特别是当多个装饰者相互影响时,可能需要谨慎地确定装饰者的添加顺序。

  3. 性能开销:每个装饰者都会增加额外的开销,因为它们通过组合的方式提供功能,而不是通过继承来实现。在一些性能敏感的场景中,这可能会产生一定的性能开销。

综上所述,装饰者模式在需要动态地为对象添加功能而又不想改变其原始类结构时非常有用,但需要权衡其复杂性和性能开销。

使用场景

装饰者模式通常在以下情况下被使用:

  1. 动态地为对象添加功能:当需要在运行时动态地为对象添加额外的功能,而不影响其原始类或修改其代码时,装饰者模式非常有用。例如,在不同的情况下,我们可能需要向咖啡中添加不同的调料,而不需要修改咖啡类本身。

  2. 避免类爆炸:当有许多类似但不完全相同的对象需要被创建时,使用继承可能会导致类爆炸。在这种情况下,装饰者模式可以通过组合的方式来动态地添加功能,而不是通过继承来创建大量子类。

  3. 单一职责原则:装饰者模式可以帮助我们遵循单一职责原则,即一个类应该只负责一个职责。通过将功能拆分成单独的装饰者类,每个类只负责一个功能,从而提高了代码的可维护性和可扩展性。

  4. 开放封闭原则:装饰者模式也有助于遵循开放封闭原则,即对扩展开放,对修改封闭。通过添加新的装饰者类来扩展功能,而不需要修改现有代码。

  5. 需要动态地移除功能:与添加功能类似,装饰者模式也可以用于动态地移除对象的功能。由于装饰者可以根据需要添加或移除,因此在某些情况下,这种动态性非常有用。

  6. 支持嵌套装饰:装饰者模式支持嵌套装饰,即一个装饰者可以装饰另一个装饰者,从而形成复杂的功能组合。

总的来说,装饰者模式适用于需要动态地添加或移除对象功能,并且希望避免类爆炸以及遵循单一职责和开放封闭原则的情况下。它提供了一种灵活且可扩展的方式来组合对象功能,而不需要修改其原始类结构。

JDK源码解析

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

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

public class Demo {
    public static void main(String[] args) throws Exception{
        //创建BufferedWriter对象
        //创建FileWriter对象
        FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);

        //写数据
        bw.write("hello Buffered");

        bw.close();
    }
}

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

小结:

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

装饰者模式(Decorator Pattern)和代理模式(Proxy Pattern)的区别:

装饰者模式和代理模式是两种常见的设计模式,它们在实现上有一些区别:

装饰者模式:

  1. 用途:装饰者模式用于动态地为对象添加额外的功能,而不修改其原始类的结构。它通过组合的方式实现功能的添加和组合。

  2. 结构:装饰者模式通常包括一个抽象组件(Component)、具体组件(Concrete Component)、装饰者(Decorator)和具体装饰者(Concrete Decorator)。装饰者维护一个指向组件对象的引用,并实现与组件相同的接口,通过递归调用实现功能的装饰。

  3. 动态性:装饰者模式允许在运行时动态地添加或移除功能,因为装饰者对象可以随时添加到对象上。

代理模式:

  1. 用途:代理模式用于控制对对象的访问。代理通常在客户端和实际对象之间起到中介的作用,可以用于实现延迟加载、访问控制、远程代理等场景。

  2. 结构:代理模式通常包括一个抽象主题(Subject)、具体主题(Real Subject)和代理(Proxy)。代理实现与主题相同的接口,并在内部持有一个真实主题的引用,在方法调用时代理可以在调用前后进行额外的处理。

  3. 目的:代理模式的主要目的是控制对对象的访问,而不是为对象添加功能。代理可以在客户端和实际对象之间提供间接层,从而实现对实际对象的控制。

总结:

  • 装饰者模式用于动态地为对象添加功能,而代理模式用于控制对对象的访问。
  • 装饰者模式通过组合的方式实现功能的添加和组合,而代理模式则通常通过在客户端和实际对象之间添加一个代理对象来实现控制。
  • 装饰者模式主要关注功能的增强和组合,而代理模式主要关注对对象的访问控制。
  • 获取目标对象构建的地方不同
    装饰者是由外界传递进来,可以通过构造方法传递
    静态代理是在代理类内部创建,以此来隐藏目标对象

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

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

相关文章

Doris实战——结合Flink构建极速易用的实时数仓

目录 一、实时数仓的需求与挑战 二、构建极速易用的实时数仓架构 三、解决方案 3.1 如何实现数据的增量与全量同步 3.1.1 增量及全量数据同步 3.1.2 数据一致性保证 3.1.3 DDL 和 DML 同步 Light Schema Change Flink CDC DML 和DDL同步 3.2 如何基于Flink实现多种数…

小程序应用、页面、组件生命周期

引言 微信小程序生命周期是指在小程序运行过程中,不同阶段触发的一系列事件和函数。这一概念对于理解小程序的整体架构和开发流程非常重要。本文将介绍小程序生命周期的概念以及在不同阶段触发的关键事件,帮助开发者更好地理解和利用小程序的生命周期。 …

2024程序员容器化上云之旅-第4集-Ubuntu-WSL2-Windows11版:夺取宝剑

故事梗概 Java程序员马意浓在互联网公司维护老旧电商后台系统。 渴望学习新技术的他在工作中无缘Docker和K8s。 他开始自学Vue3并使用SpringBoot3完成了一个前后端分离的Web应用系统,并打算将其用Docker容器化后用K8s上云。 6 夺取宝剑 🔥阅读Nigel…

Coursera吴恩达机器学习专项课程02:Advanced Learning Algorithms 笔记 Week01

Advanced Learning Algorithms Week 01 笔者在2022年7月份取得这门课的证书,现在(2024年2月25日)才想起来将笔记发布到博客上。 Website: https://www.coursera.org/learn/advanced-learning-algorithms?specializationmachine-learning-in…

3. Java中的锁

文章目录 乐观锁与悲观锁乐观锁(无锁编程,版本号机制)悲观锁两种锁的伪代码比较 通过 8 种锁运行案例,了解锁锁相关的 8 种案例演示场景一场景二场景三场景四场景五场景六场景七场景八 synchronized 有三种应用方式8 种锁的案例实际体现在 3 个地方 从字节码角度分析 synchroni…

亿道推出重磅加固平板!为行业发展注入新动力

随着科技生产力的不断发展,各行各业都得到质的飞跃。产品的迭代速度也大大加快,作为全球领先的加固行移动终端一站式提供商,亿道信息跟紧时代潮流,推出EM-I10J、EM-I20J两款均衡型加固平板,为行业发展注入新动力。 接地…

.[hudsonL@cock.li].mkp勒索加密数据库完美恢复---惜分飞

有朋友oracle数据库所在机器被加密,扩展名为:.[hudsonLcock.li].mkp,数据文件类似: 通过专业工具分析,确认这次运气非常好,每个文件就加密破坏前面31个block 通过研发的Oracle数据文件勒索恢复工具进行恢复 顺利数据库并且导出数据 mkp勒索病毒预…

【大厂AI课学习笔记NO.55】2.3深度学习开发任务实例(8)模型训练

作者简介:giszz,腾讯云人工智能从业者TCA认证,信息系统项目管理师。 博客地址:https://giszz.blog.csdn.net 声明:本学习笔记来自腾讯云人工智能课程,叠加作者查阅的背景资料、延伸阅读信息,及学…

CDQ分治详解,一维、二维、三维偏序

文章目录 零、偏序关系一、一维偏序二、二维偏序三、三维偏序(CDQ)3.1CDQ分治3.2CDQ分治解决三维偏序的流程 四、OJ练习4.1三维偏序模板题4.1.1原题链接4.1.2AC代码 4.2老C的任务4.2.1原题链接4.2.2解题思路4.2.3AC代码 4.3动态逆序对4.3.1原题链接4.3.2解题思路4.3.3AC代码 零…

C# 学习第二弹

一、变量 存储区(内存)中的一个存储单元 (一)变量的声明和初始化 1、声明变量——根据类型分配空间 ①声明变量的方式 —变量类型 变量名 数值; —变量类型 变量名; 变量名 数值; —变…

使用R语言进行主成分和因子分析

一、数据描述 数据来源2013年各地区水泥制造业规模以上企业的各主要经济指标,原始数据来源于2014年(《中国水泥统计年鉴》),试对用主成分和因子进行经济效益评价。 地区,企业个数(亿元),流动资产合计&…

python Matplotlib Tkinter-->最终框架一

3D雷达上位机实例(能够通过点击柱状图来展示3D雷达数据)2024.2.26 环境 python:python-3.12.0-amd64 包: matplotlib 3.8.2 pillow 10.1.0 import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk impor…

基于Springboot + Vue 母婴商城系统

末尾获取源码作者介绍:大家好,我是墨韵,本人4年开发经验,专注定制项目开发 更多项目:CSDN主页YAML墨韵 学如逆水行舟,不进则退。学习如赶路,不能慢一步。 目录 一、项目简介 二、开发技术与环…

基于Java SSM框架实现驾校预约管理系统项目【项目源码+论文说明】计算机毕业设计

基于java的SSM框架实现驾校预约管理系统演示 摘要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势,驾校预约管理系统当然也不能排除在外,随着网络市场的不断成熟,带动了驾校…

【BUG 记录】史诗级 BUG - MYSQL 删库删表却没有备份如何恢复数据

【BUG 记录】史诗级 BUG - MYSQL 删库删表却没有备份如何恢复数据 1. 问题描述2. 解决方案(binlog)2.1 构造测试环境2.2 查看 MySQL 环境是否开启 binlog2.3 查看所有的 binlog 日志记录2.4 查看当前正在使用的是哪一个 binlog 文件2.5 查看此时的 binlo…

设计并实现一个并发安全的LRU(Least Recently Used,最近最少使用)缓存结构

文章目录 前言实战演示写在最后 前言 相信很多人都使用过LinkedHashMap,LinkedHashMap中的removeEldestEntry可以删除老旧的元素,我们可以以此来实现一个LRU缓存结构,并结合java中JUC包中的各种多线程锁机制来保证多线程安全。 以下是我遇见…

C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN Yolov8-OBB 旋转目标检测 效果 模型信息 Model Properties ------------------------- date:2024-02-26T08:38:44.171849 description:Ultralytics YOLOv8s-obb model trained on runs/DOT…

Windows常用协议

LLMNR 1. LLMNR 简介 链路本地多播名称解析(LLMNR)是一个基于域名系统(DNS)数据包格式的协议,可用于解析局域网中本地链路上的主机名称。它可以很好地支持IPv4和IPv6,是仅次于DNS 解析的名称解析协议。 2.LLMNR 解析过程 当本地hosts 和 DNS解析 当本地hosts 和 …

Linux浅学笔记04

目录 Linux实用操作 Linux系统下载软件 yum命令 apt systemctl命令 ln命令 日期和时区 IP地址 主机名 网络传输-下载和网络请求 ping命令 wget命令 curl命令 网络传输-端口 进程 ps 命令 关闭进程命令: 主机状态监控命令 磁盘信息监控&#xff1a…

2018-02-14 新闻内容爬虫【上学时做论文自己爬新闻数据,原谅我自己懒发的图片】

2018-02-14新闻内容爬虫【上学时做论文自己爬新闻数据,原谅我自己懒发的图片】资源-CSDN文库https://download.csdn.net/download/liuzhuchen/88878591爬虫过的站点: 1QQ新闻 1,准备爬取滚动新闻页面 2 通过F12 开发工具查找发现&#xff…