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

在这里插入图片描述

装饰者设计模式

装饰者设计模式(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,"炒面");}@Overridepublic float cost() {return getPrice();}
}
/*** @author OldGj 2024/02/25* @version v1.0* @apiNote 炒饭类 - 具体构件者(待装饰类)*/
public class FriedRice extends FastFood {public FriedRice() {super(10, "炒饭");}@Overridepublic 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 {@Overridepublic String getDesc() {return super.getDesc()+getFastFood().getDesc();}public Egg(FastFood fastFood) {super(fastFood, 1, "鸡蛋");}@Overridepublic 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, "培根");}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}@Overridepublic 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 {@Overridepublic double cost() {return 1.99;}@Overridepublic String getDescription() {return "Espresso";}
}public class Latte implements Coffee {@Overridepublic double cost() {return 2.49;}@Overridepublic String getDescription() {return "Latte";}
}

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

public abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee decoratedCoffee) {this.decoratedCoffee = decoratedCoffee;}@Overridepublic double cost() {return decoratedCoffee.cost();}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}
}

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

public class Milk extends CoffeeDecorator {public Milk(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic double cost() {return super.cost() + 0.5; // 增加牛奶的价格}@Overridepublic String getDescription() {return super.getDescription() + ", Milk";}
}public class Caramel extends CoffeeDecorator {public Caramel(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic double cost() {return super.cost() + 0.7; // 增加焦糖的价格}@Overridepublic 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. 目的:代理模式的主要目的是控制对对象的访问,而不是为对象添加功能。代理可以在客户端和实际对象之间提供间接层,从而实现对实际对象的控制。

总结:

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

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

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

相关文章

Selenium IDE插件录制网页,解放双手

1、 国内下载地址 https://www.crx4chrome.com/crx/77585/ ,这个网络正常基本可以下载,目前最新版本是3.17.2。 点击Crx4Chrome下载。下载后的文件名称是:mooikfkahbdckldjjndioackbalphokd-3.17.2-Crx4Chrome.com.crx。 2、 安装 直接打开…

探索创造无限可能——Autodesk AutoCAD 2022(CAD 2022)系统要求

随着科技的不断进步和发展,计算机辅助设计(CAD)已经成为现代设计行业中不可或缺的一部分。在众多CAD软件中,Autodesk AutoCAD 2022(CAD 2022)无疑是最受欢迎和广泛应用的一款软件。作为一款全球领先的CAD软…

sql 行列互换

在SQL中进行行列互换可以使用PIVOT函数。下面是一个示例查询及其对应的结果: 创建测试表格 CREATE TABLE test_table (id INT PRIMARY KEY,name VARCHAR(50),category VARCHAR(50) );向测试表格插入数据 INSERT INTO test_table VALUES (1, A, Category A); INSE…

某电力铁塔安全监测预警系统案例分享

项目概述 电力铁塔是承载电力供应的重要设施,它的安全性需要得到可靠的保障。但是铁塔一般安装在户外,分布广泛,且有很多安装在偏远地区,容易受到自然、人力的影响和破环。因此需要使用辅助的方法实时监控铁塔的安全状态&#xff…

计算机设计大赛 深度学习大数据物流平台 python

文章目录 0 前言1 课题背景2 物流大数据平台的架构与设计3 智能车货匹配推荐算法的实现**1\. 问题陈述****2\. 算法模型**3\. 模型构建总览 **4 司机标签体系的搭建及算法****1\. 冷启动**2\. LSTM多标签模型算法 5 货运价格预测6 总结7 部分核心代码8 最后 0 前言 &#x1f5…

office word保存pdf高质量设置

1 采用第三方pdf功能生成 分辨率越大质量越好

MySQL集群 双主架构(配置命令)

CSDN 成就一亿技术人&#xff01; 今天刚开学第一天给大家分享一期&#xff1a;MySQL集群双主的配置需求和命令 CSDN 成就一亿技术人&#xff01; 神秘泣男子主页&#xff1a;作者首页 <———— MySQL专栏 &#xff1a;MySQL数据库专栏<———— MySQL双主是一…

Oracle 基础表管理(Heap-Organized Table Management)

表是数据库中负责数据存储的对象&#xff0c;在RDBMS中&#xff0c;数据以行、列的形式存储在表中。Oracle中表有很多种类型&#xff0c;最基础且应用最常用的类型就是堆表&#xff08;Heap-Organized Table&#xff09;&#xff0c;本文列举了Oracle堆表的常用管理操作。 一、…

pytorch --反向传播和优化器

1. 反向传播 计算当前张量的梯度 Tensor.backward(gradientNone, retain_graphNone, create_graphFalse, inputsNone)计算当前张量相对于图中叶子节点的梯度。 使用反向传播&#xff0c;每个节点的梯度&#xff0c;根据梯度进行参数优化&#xff0c;最后使得损失最小化 代码…

React Hooks概述及常用的React Hooks介绍

Hook可以让你在不编写class的情况下使用state以及其他React特性 useState ● useState就是一个Hook ● 通过在函数组件里调用它来给组件添加一些内部state,React会在重复渲染时保留这个state 纯函数组件没有状态&#xff0c;useState()用于设置和使用组件的状态属性。语法如下…

Qt的QThread、QRunnable和QThreadPool的使用

1.相关描述 随机生产1000个数字&#xff0c;然后进行冒泡排序与快速排序。随机生成类继承QThread类、冒泡排序使用moveToThread方法添加到一个线程中、快速排序类继承QRunnable类&#xff0c;添加到线程池中进行排序。 2.相关界面 3.相关代码 widget.cpp #include "widget…

实验室储样瓶耐强酸强碱PFA材质试剂瓶适用新材料半导体

PFA&#xff0c;全名可溶性聚四氟乙烯&#xff0c;试剂瓶又叫取样瓶、样品瓶、广口瓶、储样瓶等。主要用于痕量分析、同位素分析等实验室&#xff0c;广泛应用于新兴的半导体、新材料、多晶硅、硅材、微电子等行业。 规格参考&#xff1a;30ml、60ml、100ml、125ml、250ml、30…

C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径

C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径 参考博文: 1.C++笔记之执行一个可执行文件时指定动态库所存放的文件夹lib的路径 2.Linux笔记之LD_LIBRARY_PATH详解 3.qt-C++笔记之使用QProcess去执行一个可执行文件时指定动态库所存放的文件夹lib的路径 c…

如何将本地项目上传到github上

将本地项目上传到github上有很多种方法&#xff0c;这里只讲述我认为最简单快捷的一种&#xff0c;先在github中创建一个仓库&#xff0c;接着在本地建文件夹&#xff0c;用命令行将项目推送到本地仓库&#xff0c;然后连接远程仓库&#xff0c;将本地项目推送到远程仓库上。要…

时间序列分析实战(四):Holt-Winters建模及预测

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

Jessibuca 插件播放直播流视频

jessibuca官网&#xff1a;http://jessibuca.monibuca.com/player.html git地址&#xff1a;https://gitee.com/huangz2350_admin/jessibuca#https://gitee.com/link?targethttp%3A%2F%2Fjessibuca.monibuca.com%2F 项目需要的文件 1.播放组件 <template ><div i…

3. Java中的锁

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

CentOS 7全系列免费

CentOS 7 全系列免费&#xff1a;桌面版、工作站版、服务器版等等………… 上文&#xff0c;关于CentOS 7这句话&#xff0c;被忽略了。 注意版本&#xff1a;知识产权、网络安全。

2024河北国际光伏展

2024河北国际光伏展是一个专门展示和促进光伏技术与产业发展的国际性展览会。该展览会将于2024年在中国河北省举办&#xff0c;吸引来自世界各地的光伏企业、专家、学者和投资者参加。 展览会将展示最新的光伏技术和产品&#xff0c;包括太阳能电池板、光伏组件、逆变器、储能系…

ChatGPT 国内快速上手指南

ChatGPT简介 ChatGPT是由OpenAI团队研发的自然语言处理模型&#xff0c;该模型在大量的互联网文本数据上进行了预训练&#xff0c;使其具备了深刻的语言理解和生成能力。 GPT拥有上亿个参数&#xff0c;这使得ChatGPT在处理各种语言任务时表现卓越。它的训练使得模型能够理解上…