程序员必知!装饰模式的实战应用与案例分析

news2024/11/18 3:36:31

程序员必知!装饰模式的实战应用与案例分析 - 程序员古德

装饰模式是一种结构型设计模式,允许在不改变对象基础上动态添加职责或行为。举个咖啡店中咖啡定制的实际例子,顾客可选不同配料装饰咖啡,每个配料视作装饰器,装饰模式优点有动态扩展、灵活性和避免类爆炸,但可能增加系统复杂性,需权衡使用。

定义

程序员必知!装饰模式的实战应用与案例分析 - 程序员古德

装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在不改变对象自身的基础上动态地给对象添加一些额外的职责或行为,装饰模式提供了一种灵活的方式来扩展对象的功能,可以在运行时根据需要动态地添加或撤销功能。

在实际业务中,一个常见的例子是咖啡店中的咖啡定制,假设有一个基础的咖啡(Base Coffee),顾客可以选择添加不同的配料(如牛奶、糖、巧克力酱等)来装饰他们的咖啡,每个配料都可以看作是一个装饰器(Decorator),它们可以在基础咖啡上添加新的口味或外观,从而产生各种各样的定制咖啡。

在这个例子中,基础咖啡类可以定义一个提供基础咖啡的方法,而每个配料类(装饰器)都可以继承或包装基础咖啡类,并添加自己的特定行为或状态,顾客可以通过将基础咖啡与不同的配料组合在一起,创建出符合自己口味的定制咖啡。

使用装饰模式的优点有,1、可以在运行时动态地添加或撤销对象的职责,而不需要修改对象的代码,2、可以自由地组合和排列不同的装饰器,以创建出各种定制化的对象,3、通过使用装饰模式,可以避免为了支持各种组合而创建大量的子类,从而简化类结构。

代码案例

程序员必知!装饰模式的实战应用与案例分析 - 程序员古德

首先,看一下未使用装饰模式时的实现,假设我们有一个Component接口,它定义了operation方法,然后,我们有一个ConcreteComponent类,它实现了Component接口,现在,如果我们想要给ConcreteComponent添加额外的功能,我们可能会直接在ConcreteComponent类中添加新的方法或修改现有的方法,但是,这种方式违反了开放封闭原则,因为我们对现有的类进行了修改。

以下是一个未使用装饰模式的示例代码,如下代码:

// 组件接口  
interface Component {  
    void operation();  
}  
  
// 具体组件  
class ConcreteComponent implements Component {  
    @Override  
    public void operation() {  
        System.out.println("执行具体操作");  
    }  
      
    // 添加额外功能的方法  
    public void additionalFunctionality() {  
        System.out.println("执行额外功能");  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        ConcreteComponent component = new ConcreteComponent();  
        component.operation(); // 执行具体操作  
        component.additionalFunctionality(); // 执行额外功能  
    }  
}

在上面的代码中,我们在ConcreteComponent类中直接添加了additionalFunctionality方法来提供额外的功能,这种方式的问题是,如果我们想要给ConcreteComponent添加更多的功能,或者想要在运行时动态地添加或删除功能,我们就需要不断地修改ConcreteComponent类,这会导致代码变得复杂且难以维护。

为了解决这个问题,我们可以使用装饰模式,装饰模式允许我们在不修改现有类的情况下,动态地给对象添加额外的功能,通过创建一个实现了与原始类相同接口的装饰类,我们可以在运行时将装饰类包装在原始类对象上,从而给原始类对象添加额外的功能,但是,上面的代码并没有使用装饰模式,而是直接在类中添加了额外的方法。

下面是一个使用装饰模式的Java代码示例,首先,我们定义一个Beverage接口,如下代码:

// 基础饮品接口  
public interface Beverage {  
    String getDescription(); // 获取饮品的描述  
    double cost(); // 计算饮品的成本  
}

接下来,我们定义一个具体的Coffee类来实现这个接口,如下代码:

// 具体的咖啡类,实现了Beverage接口  
public class Coffee implements Beverage {  
    @Override  
    public String getDescription() {  
        return "Coffee";  
    }  
  
    @Override  
    public double cost() {  
        return 1.99;  
    }  
}

然后,我们创建一个装饰器抽象类,它也实现了Beverage接口,并包含一个Beverage对象的引用,如下代码:

// 装饰器抽象类,也实现了Beverage接口,用于装饰其他Beverage对象  
public abstract class BeverageDecorator implements Beverage {  
    protected Beverage beverage; // 被装饰的饮品对象  
  
    // 通过构造函数传递被装饰的对象  
    public BeverageDecorator(Beverage beverage) {  
        this.beverage = beverage;  
    }  
  
    // 实现接口的getDescription方法,委托给被装饰的对象  
    @Override  
    public String getDescription() {  
        return beverage.getDescription();  
    }  
  
    // 实现接口的cost方法,委托给被装饰的对象  
    @Override  
    public double cost() {  
        return beverage.cost();  
    }  
}

现在我们可以创建具体的装饰器类来添加调料,如下代码:

// 具体的装饰器类:添加牛奶  
public class Milk extends BeverageDecorator {  
    public Milk(Beverage beverage) {  
        super(beverage); // 调用父类的构造函数,传递被装饰的对象  
    }  
  
    @Override  
    public String getDescription() {  
        return beverage.getDescription() + ", Milk"; // 添加描述信息  
    }  
  
    @Override  
    public double cost() {  
        return beverage.cost() + 0.30; // 增加成本  
    }  
}  
  
// 具体的装饰器类:添加糖浆  
public class Syrup extends BeverageDecorator {  
    public Syrup(Beverage beverage) {  
        super(beverage); // 调用父类的构造函数,传递被装饰的对象  
    }  
  
    @Override  
    public String getDescription() {  
        return beverage.getDescription() + ", Syrup"; // 添加描述信息  
    }  
  
    @Override  
    public double cost() {  
        return beverage.cost() + 0.50; // 增加成本  
    }  
}

最后,我们创建一个客户端类来演示如何使用这些类,如下代码:

// 客户端类,用于演示装饰模式的使用  
public class Client {  
    public static void main(String[] args) {  
        // 创建一个基础的咖啡对象  
        Beverage beverage = new Coffee();  
        System.out.println(beverage.getDescription() + " $" + beverage.cost()); // 输出:Coffee $1.99  
        System.out.println("-----------------------");  
          
        // 使用装饰器动态添加调料(牛奶和糖浆)到咖啡中  
        beverage = new Syrup(new Milk(beverage)); // 先加牛奶,再加糖浆(顺序可以调换)  
        System.out.println(beverage.getDescription() + " $" + beverage.cost()); // 输出:Coffee, Milk, Syrup $2.79 (1.99 + 0.30 + 0.50)  
    }  
}

首先在Beverage接口中定义了两个方法,getDescription()用于获取饮品的描述,cost()用于计算饮品的成本,所有饮品和装饰器都实现这个接口,确保它们有共同的行为。Coffee类实现了Beverage接口,表示一种具体的饮品——咖啡,它提供了自己的描述和成本。
BeverageDecorator也实现了Beverage接口,但它不是一种具体的饮品,而是一个可以用来“装饰”其他饮品的类,它包含一个对Beverage对象的引用,可以是任何实现了Beverage接口的对象(包括其他装饰器)。具体装饰器类MilkSyrup继承自BeverageDecorator,每个类都表示一种可以添加到饮品中的调料,它们重写了getDescription()cost()方法来修改被装饰饮品的描述和成本。

在类Client创建了一个Coffee对象,然后使用MilkSyrup装饰器来动态地添加调料。注意,装饰器的顺序很重要,因为它会影响最终饮品的描述和成本。

核心总结

程序员必知!装饰模式的实战应用与案例分析 - 程序员古德

装饰模式是Java中的一种重要设计模式,主要用于动态地给一个对象添加一些额外的职责。其优点在于可以在不改变原有类的基础上,动态地扩展功能,提供了比继承更多的灵活性,同时,装饰模式可以使用多个装饰器来装饰一个对象,实现功能的叠加,此外,装饰模式还具有很好的可扩展性,新的功能可以通过定义新的装饰器来添加。

然而,装饰模式也存在一些缺点,由于装饰器与具体构件有相同的接口,可能会导致系统中出现过多的相似接口,增加了系统的复杂性,同时,在多层装饰的情况下,代码的调试和维护可能会变得相对困难。

在使用装饰模式时,建议明确区分装饰器和具体构件的接口,以减少系统的复杂性,同时,应尽量避免过多的装饰层次,以降低代码的维护难度,在需要动态添加功能且不希望修改原有类的情况下,可以考虑使用装饰模式。

关注我,每天学习互联网编程技术 - 程序员古德

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

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

相关文章

Nacos 持久化及集群的搭建【微服务】

文章目录 一、统一配置管理二、微服务配置拉取三、配置热更新四、多环境共享配置五、Nacos 集群搭建1. 集群结构2. 初始化数据库3. 搭建集群 六、Nginx 反向代理七、启动项目测试 一、统一配置管理 案例练习的时候我们只有两个微服务,管理起来非常简单,但…

虎克:开发小程序要多少钱一个,非专业开发如何做自己的小程序

小程序开发费用主要取决于小程序的功能复杂度和开发周期。一般来说,小程序开发费用可以分为两类:模板开发和定制开发。 模板开发:模板开发是指使用现成的模板进行开发,价格相对较低,一般在几千元左右。优点是价格便宜&…

SpringSecurity-2.7中跨域问题

SpringSecurity-2.7中跨域问题 访问测试 起因 写这篇的起因是会了解到 SSM(CrosOrigin)解决跨域,但是会在加入SpringSecurity配置后,这个跨域解决方案就失效了,而/login这个请求上是无法添加这个注解或者通过配置(WebMvcConfig)去解决跨域,所以只能使用SpringSecurity提供的.c…

【接口自动化】写接口自动化case要注意的点!

可能有人会说,写接口的自动化CASE多简单了,写个参数发送请求完事了,还要注意啥? 没错,相比起UI自动化的case,你要去写各种定位器,接口自动化的case写起来确实容易多了。这也是接口自动化的一个…

APM32 移植 RTX5

打开APM32的 一个官方例子或者模板工程, MKD格式 更改工程属性,将编译器换为V6: 然后勾选 Use MicroLIB; 接着: 接着: 接着确认 然后编译,会报260个错误: 然后 打开工程树中 下面的文件: 如果用的是M4内核,添加下面的定义:

2SK3019 中低压MOSFET 60V 100mA 双N通道 SOT-723封装

2SK3019小电流双N通道MOSFET,电压60V电流100mA,采用SOT-723封装形式。适用于低RDS (on)的高密度电池设计,压控小信号开关,坚固可靠,ESD保护。可应用便携式设备的负载开关,电池开关上。

探索 CodeWave低代码技术的魅力与应用

目录 前言1 低代码平台2 CodeWave简介3 CodeWave 的独特之处3.1 高保真还原交互视觉需求3.2 擅长复杂应用开发3.3 支持应用导出&独立部署3.4 金融级安全要求3.5 可集成性高3.6 可拓展性强 4 平台架构和核心功能4.1 数据模型设计4.2 页面设计4.3 逻辑设计4.4 流程设计4.5 接…

【STM32F103】TIM定时器PWM

定时器分类 STM32F1中除了互联型产品(STM32F103C8T6为64KB Flash 中容量产品),其余有8个定时器。 可以8个定时器分为高级,通用,基本三种。 高级定时器有两个,分别是TIM1和TIM8。 通用定时器有四个&…

SD-WAN:提升连锁零售企业异地组网稳定性

连锁零售企业往往拥有众多分布在不同地区的分支机构和零售店,为保证企业高效运转,各地区之间的网络连接必须稳定可靠。但基于各地网络基础设施的不同和网络延迟、带宽等限制,异地组网往往并不稳定。在这背景下,SD-WAN成为连锁零售…

频谱论文:空间频率插值的无线电地图 Space-Frequency-Interpolated Radio Map

#频谱# K. Sato, K. Suto, K. Inage, K. Adachi and T. Fujii, "Space-Frequency-Interpolated Radio Map," in IEEE Transactions on Vehicular Technology, vol. 70, no. 1, pp. 714-725, Jan. 2021, doi: 10.1109/TVT.2021.3049894. 东京理科大学&#xff0…

MBP的新拉力=800MT+凯越525+泛美?

MBP国内新兴品牌,可能部分车友了解,之前专门做欧洲市场的,米兰车展的时候它带来一款比较有意思的车型,型号:T502X,这个502和贝纳利的502关系不大。 据了解,车辆是由高金代工生产的,发动机就是高…

闲置树莓派(linux单片机)做NAS的最简单方案

闲置的树莓派其实可以做nas服务器使用。树莓派单片机5V2A,功耗大概10W。外置移动硬盘盒子12V2A 功耗大概24W。全加起来,功耗一小时30W,一天3角左右的运行成本,速率也可以,非常划算。(其他任何Linux单片机原…

2024年个人工作计划怎么写?新年待办计划这样写更方便

元旦的钟声还在耳边回响,2024年的新篇章已经开启。面对新的一年,我深知一个清晰、实用的个人工作计划是多么重要。它不仅是指引我前进的灯塔,更是我实现目标、提升效率的秘密武器。 但如何制定这样一个计划呢?在过去,…

边框渐变色+圆角

这两个同时写,圆角不会生效 li {width: 282px;height: 96px;margin-right: 20px;box-sizing: border-box;border: 1px solid transparent;background-image: linear-gradient(#fff, #fff),linear-gradient(to right, #8efd9f, #07cbfb);background-origin: border-b…

【C语言】作用域 和 生命周期

🚩 WRITE IN FRONT 🚩 🔎 介绍:"謓泽"正在路上朝着"攻城狮"方向"前进四" 🔎🏅 荣誉:2021|2022年度博客之星物联网与嵌入式开发TOP5|TOP4、2021|2222年获评…

Sectigo企业的通配符证书续费

首先,我们要知道SSL证书是有期限的,通常SSL证书的有效期为一年,在SSL证书到期之前我们需要对其进行续费,从而延续SSL证书对网站的保护。Sectigo是中多的CA认证机构之一,申请速度快、性价比高,很多子域名站点…

如何用python新建一个文件,python怎么新建一个文件

大家好,小编来为大家解答以下问题,如何通过python新建一个文件中的文件,如何通过python新建一个文件夹并命名,现在让我们一起来看看吧! 文章目录 前言 1. 使用内置的open函数2. 使用with关键字3. 使用os模块总结零基础…

值得推荐的 5 个前端性能测试工具

前言 PageSpeed Insights 谷歌开发的一个免费的网页分析工具,在地址栏中输入被分析的网站url地址,点击分析, 可模拟移动设备访问页面结果分析 桌面设备访问页面结果分析 前端开发工程师,可以根据这个报告进行页面优化 Lighthous…

HTML语义化的理解

HTML语义化是指在编写HTML代码时,合理地选择适当的标签和属性来描述页面的结构和内容,使得代码更具有可读性、可维护性和可访问性。 可读性:通过使用语义化的标签,可以清晰地表达页面的结构和内容,使得代码更易于阅读和…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)在EventLoop的任务队列中添加新任务

任务队列是一个链表,每个节点包含channel类型、文件描述符和操作类型。在添加节点时,需要考虑线程同步,并确保节点被正确地添加到链表中。节点的操作可以写到另一个函数中,以便于程序的维护。在添加任务节点时,需要加互…