探索设计模式的魅力:为什么你应该了解装饰器模式-代码优化与重构的秘诀

在这里插入图片描述


设计模式专栏:http://t.csdnimg.cn/nolNS


开篇

    在一个常常需要在不破坏封装的前提下扩展对象功能的编程世界,有一个模式悄无声息地成为了高级编程技术的隐形冠军。我们日复一日地享受着它带来的便利,却往往对其背后的复杂性视而不见。它是怎样织入我们代码的丝线的呢?这个编程世界里的‘变形金刚’,究竟隐藏着什么秘密?让我们一起揭开装饰器模式的神秘面纱。

一、背景

    在软件开发中,由于需求的变化非常频繁,因此需要有一种方式来在不改变原有代码的基础上增加新功能。装饰器模式正好满足了这一需求。
    变化是唯一不变的定律。因此,使用装饰器模式可以帮助开发人员在不断变化的需求中保持代码的灵活性和可维护性。

二、装饰器模式概述

2.1 简介

    装饰器模式是一种设计模式,用于在不修改原始类代码的情况下,动态地给对象添加新的功能。它通过创建一个包装对象来包裹原有对象,并在包装对象中添加额外的职责。这样,通过在运行时动态地创建和附加这些包装对象,可以动态地扩展对象的功能。

2.2 场景

1. GUI设计:
    在图形用户界面(GUI)开发中,装饰器模式可以用于自定义组件的行为。例如,假设你有一个基本的文本框组件,你可以使用装饰器模式为其添加额外的功能,比如校验输入、实时自动完成等。通过装饰器模式,你可以在不改变原始组件代码的情况下,动态地为组件添加功能。
2. 数据流处理:
    在数据处理流程中,装饰器模式可以用于对数据流进行过滤或转换。例如,假设你有一组数据流(比如通过传感器采集的数据),你可以使用装饰器模式为数据流添加解密、压缩或加密等功能。通过装饰器模式,你可以在不修改原始数据流代码的情况下,增加新的数据处理能力。
3. 日志记录:
    在日志记录系统中,装饰器模式可以用于为不同的日志记录类添加额外的功能。例如,你可能有一个基本的日志记录器,可以将日志信息写入文件。你可以使用装饰器模式为该日志记录器添加时间戳或格式化日志信息等功能,而无需修改原始的日志记录器代码。
5. 身份验证:
    在身份验证过程中,装饰器模式可以用于为已有的身份验证系统添加额外的验证功能。例如,假设你有一个基本的用户身份验证系统,你可以使用装饰器模式为该系统添加双因素身份验证、IP白名单验证等功能。

三、深入装饰器模式的结构

3.1 核心组件解析

1. Component:
    这是被装饰的对象的基本接口或抽象类,定义了对象必须实现的操作。装饰器和具体的被装饰对象都应当实现这个接口。
2. ConcreteComponent:
    这是Component接口的一个具体实现类。它定义了原始对象的基本功能,也即我们打算添加新功能的对象。
3. Decorator:
    这是一个抽象类,它实现(或继承)Component接口并包含一个Component类型的实例变量。这个类通常会有一个构造器,用于接收一个Component对象,并为其提供一个额外的功能层。Decorator本身通常是抽象的,因为它的目的是定义一个与ConcreteComponent兼容的接口,并为子类实现共通的任务——保持对Component实例的引用。
4. ConcreteDecorator:
    这是Decorator子类的具体实现。每个ConcreteDecorator通常实现了在Component上添加的额外功能。它会调用其内部的Component实例的方法,并在调用前后添加新的行为。

    这个模式的核心在于允许功能的层层叠加,这是通过持有Component实例的引用,并将对其的调用委托给这个实例来实现的,从而不会影响其他对象。实际上,对象可以通过一个或多个ConcreteDecorator来增加任意数量的责任。

3.2 模式原理阐述

    装饰器模式(Decorator Pattern)是一种设计模式,属于结构型模式的一种,它允许向一个现有的对象添加新的功能,而不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并且在不改变原类文件的前提下为原类动态地添加功能。
    装饰器模式的原理可以用以下几个要点来概括:
1. 继承关系的替代:
    装饰器模式提供了一种灵活的替代扩展系统的方法,而不是通过继承增加子类的方式来扩展功能,因为继承是静态的,并且应用于整个类,而装饰器模式可以在运行时动态添加特定对象的功能。
2. 接口一致性:
    装饰器模式使用组件(Component)接口对客户端透明,这意味着客户端针对接口编程,而不是针对具体类编程。装饰器类实现或继承自同一个接口,确保装饰后的对象仍然遵循原有接口的约定。
3. 动态包装:
    在装饰器模式中,装饰类包裹着组件对象,从而在保持组件接口的同时,为组件对象添加了额外的行为(装饰)。因为多态性的原因,客户端可以自由地使用装饰后的对象。
4. 多层装饰:
    装饰器模式允许多个装饰器按顺序装饰对象,从而实时增加新的行为或职责。装饰链中的每个装饰器可以向对象添加附加的状态或行为。
5. 增加灵活性:
    装饰器模式让用户可以选择需要的装饰器来组合功能,增强了系统的灵活性,同时代码维护和扩展也变得更加方便。

四、装饰器模式的超能力

    让我们通过一个简单的装饰器模式例子来阐释这个概念。假设我们在一个饮料店打工,需要为顾客提供几种不同类型的咖啡。起初,我们只提供简单的黑咖啡,但是顾客可以选择添加多种调料,如牛奶、糖、摩卡和奶泡。
    首先,我们建立一个饮料的抽象类,并让所有的咖啡和调料装饰器继承这个类:
// 抽象组件,定义饮料
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() {return description;}public abstract double cost();
}

    现在,我们添加我们的基础组件,黑咖啡:

// 具体组件,实现抽象组件
public class BlackCoffee extends Beverage {public BlackCoffee() {description = "Black Coffee";}public double cost() {return 1.00;}
}

    接下来,我们定义装饰器的抽象类,所有的调料装饰器都应该继承它:

// 装饰器抽象类,扩展自Beverage
public abstract class CondimentDecorator extends Beverage {public abstract String getDescription();
}

    然后我们为每种调料创建具体的装饰器实现类:

// 牛奶装饰器
public class Milk extends CondimentDecorator {Beverage beverage;public Milk(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Milk";}public double cost() {return beverage.cost() + 0.20;}
}// 糖装饰器
public class Sugar extends CondimentDecorator {Beverage beverage;public Sugar(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Sugar";}public double cost() {return beverage.cost() + 0.10;}
}// 摩卡装饰器
public class Mocha extends CondimentDecorator {Beverage beverage;public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return beverage.cost() + 0.50;}
}// 奶泡装饰器
public class Whip extends CondimentDecorator {Beverage beverage;public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}public double cost() {return beverage.cost() + 0.30;}
}

    最后,我们可以这样组合它们来创建不同的咖啡:

public class CoffeeShop {public static void main(String args[]) {// Order a black coffee without any condimentsBeverage blackCoffee = new BlackCoffee();System.out.println(blackCoffee.getDescription() + " $" + blackCoffee.cost());// Add milk and sugar to the coffeeBeverage coffeeWithMilkAndSugar = new BlackCoffee();coffeeWithMilkAndSugar = new Milk(coffeeWithMilkAndSugar);coffeeWithMilkAndSugar = new Sugar(coffeeWithMilkAndSugar);System.out.println(coffeeWithMilkAndSugar.getDescription() + " $" + coffeeWithMilkAndSugar.cost());// Make a fancy coffee by adding Milk, Mocha, and Whip to the coffeeBeverage fancyCoffee = new BlackCoffee();fancyCoffee = new Milk(fancyCoffee);fancyCoffee = new Mocha(fancyCoffee);fancyCoffee = new Whip(fancyCoffee);System.out.println(fancyCoffee.getDescription() + " $" + fancyCoffee.cost());}
}

    在这个例子中,我们可以看到装饰器模式如何在不更改BlackCoffee类的情况下动态地为咖啡添加额外的调料。我们可以随意添加新的装饰器以实现新的功能,这展示了模式的灵活性。

五、装饰器模式的优缺点分析

5.1 优点

1. 增强扩展性:
    装饰器模式使得可以在不修改原始类代码的情况下通过添加装饰器在运行时扩展对象的行为。这使得遵循开闭原则(即软件实体应该对扩展开放,对修改关闭)成为可能,增加了类的灵活性和可扩展性。
2. 动态添加功能:
    与继承相比,装饰器模式能够动态地、非侵入式地添加或删除对象的责任。用户可以根据需要装饰对象,而不是在编译时决定其行为。
3. 功能组合:
    使用装饰器模式,多个装饰器可以组合起来使用,提供了一种非常灵活的方式来组合不同的功能。比如,在上面的咖啡例子中,你可以随意添加牛奶、糖、摩卡等,实现各种不同的咖啡组合。
4. 替代子类继承:
    由于使用子类继承来扩充功能可能会导致类爆炸现象(生成大量的子类),装饰器模式可以作为一种更优雅的解决方案,避免了类层次过多的缺点。
5. 保持类接口的纯净性:
    装饰器可以让我们把那些不适合放在类内部的功能,或者是那些会频繁更改的功能移出类外,这样可以保持原有的类结构清晰简单。
6. 细粒度的控制:
    装饰器模式提供了比继承更加细粒度的控制。每个装饰器可以实现更加精细的控制和管理。
7. 有利于解耦:
    装饰器模式有助于将类的核心职责和装饰功能进行分离,从而降低了系统的复杂度,并且有助于类的功能独立发展,减少了类之间的依赖关系。

5.2 缺点

1. 复杂性增加:
    使用装饰器模式可能会引入许多小类,因为每个装饰器通常对应一个小类。这有可能导致系统的结构变得复杂,特别是当装饰器过多或层次很深时。
2. 使用复杂性:
    如果使用过多的装饰器,那么客户端代码需要处理多个装饰层,这有可能使得对象的实例化过程变得复杂和难以理解。
3. 设计难度:
    正确并高效地使用装饰器模式需要对系统设计有深入理解。开发人员需要能够明确区分何时应该使用装饰器,何时应该使用简单的继承,以及何时选择其它设计模式。
4. 调试难度:
    对于装饰过的对象,调试可能更困难,因为装饰器会在调用栈中引入额外的层次。这意味着错误可能会在多个层次之间传播,而定位问题的根源可能更加耗时。
5. 接口不匹配:
    如果基础组件的接口随着时间发生变化,所有依赖于那个接口的装饰器都需要相应地进行更新,这可能会导致维护难度增加。
6. 过度使用:
    在不恰当的情况下强迫使用装饰器模式会使得设计过于复杂,简单问题可能仅仅需要更直接的方法。
7. 性能考虑:
    虽然大多数情况下这不是问题,但是在复杂的装饰器链中,每次调用都会通过所有的装饰器层,这可能会对性能产生不利影响。尤其是对于一些对性能要求高的系统,可能需要考虑额外的优化。

六、装饰器模式在现实世界中的应用

七、从装饰器模式看设计原则

7.1 开闭原则

    一个软件实体应当对扩展开放,对修改关闭。这一原则最早由BertrandMeyer [MEYER88]提出,英文原文是:Software entities should be open for extension, but closed for modification.

    这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。换言之,应当可以在不必修改源代码的情况下增强这个模块的行为。

7.2 合成复用原则

    合成/聚合复用原则 (Composite/Aggregate Reuse Principle,或 CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP)。合成/聚合复归原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象迪过向这些对象的委派达到复用已有功能的日的。

    这个设计原则有只 - 个更简短的表述:要尽量使用合成/聚合,尽量不要使用继承。

7.3 装饰器模式中的开闭原则

实践

1. 不修改现有代码的情况下扩展功能:
    装饰器模式允许在不改变原始类的源代码和不增加新的子类的情况下,动态的扩展对象的功能。通过将每个要添加的功能封装在一个装饰器类中,可以组合使用多个装饰器来增强对象的功能。
2. 运行时选择功能:
    与静态继承相比,装饰器模式通过组合的方式在运行时动态地为对象添加额外的职责,这样就可以根据需要来增加或修改对象的行为,实现开闭原则中提到的对扩展开放。
3. 维护对象接口不变:
    装饰器具备与继承的对象相同的接口,这意味着从客户端的角度看,装饰的对象与原始对象遵循相同的接口(即它们可以是透明的),这保证了原始对象的接口不受影响,同时也可以提供新的功能。
4. 松耦合设计:
    装饰器模式促进了良好的设计原则,比如单一职责原则和开闭原则,装饰器类通常专注于一个具体的功能,并且可以在不影响其他装饰器或对象的情况下单独更改。

7.3 装饰器模式中的合成复用原则

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

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

相关文章

嵌入式中物联网核心技术有哪些

IoT军事技术 物联网军事技术是一项利用IoT感知技术在军事活动中获取人、装备、作战环境状态的信息特征,从而实现在军事活动中做出智能化决策和控制局势的军事方针。 据悉,早于2012年10月军方联合了社会研究机构合力创建了“军事物联网联合实验室”。 …

机器学习入门-----sklearn

机器学习基础了解 概念 机器学习是人工智能的一个实现途径 深度学习是机器学习的一个方法发展而来 定义:从数据中自动分析获得模型,并利用模型对特征数据【数据集:特征值+目标值构成】进行预测 算法 数据集的目标值是类别的话叫做分类问题;目标值是连续的数值的话叫做回…

R语言分析任务:

有需要实验报告的可CSDN 主页个人私信 《大数据统计分析软件(R语言)》 实 验 报 告 指导教师: 专 业: 班 级: 姓 名: 学 …

[office] excel表格怎么绘制股票的CCI指标- #媒体#学习方法#笔记

excel表格怎么绘制股票的CCI指标? excel表格怎么绘制股票的CCI指标?excel表格中想要绘制一个股票cci指标,该怎么绘制呢?下面我们就来看看详细的教程,需要的朋友可以参考下 CCI指标是一种在股票,贵金属,货…

基于Python的招聘网站爬虫及可视化的设计与实现

摘要:现在,随着互联网网络的飞速发展,人们获取信息的最重要来源也由报纸、电视转变为了互联网。互联网的广泛应用使网络的数据量呈指数增长,让人们得到了更新、更完整的海量信息的同时,也使得人们在提取自己最想要的信…

Python算法题集_旋转图像

Python算法题集_旋转图像 题目48:旋转图像1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【矩阵复本】2) 改进版一【矩阵转置矩阵反转】3) 改进版二【四值旋转】 4. 最优算法 题目48:旋转图像 本文为Python算法题集之一…

Windows Server安装部署FTP服务

文章目录 建立FTP目录通过IIS在Server上安装FTP服务配置FTP站点配置身份验证和授权测试FTP服务FTP软件推荐FTP客户端软件FTP服务器软件适合Ubuntu的FTP软件 推荐阅读 在Windows操作系统中安装和配置FTP服务,主要是基于Internet Information Services (IIS)的FTP服务…

SQL sever2008中创建用户并赋权

一、创建数据库dream CREATE DATABASE dream; 二、创建登录用户XZS 法一:使用SSMS创建 通过查询 sys.syslogins 系统视图来确定当前登录是否具有系统管理员权限。执行以下查询语句: SELECT name, isntname FROM sys.syslogins WHERE sysadmin 1;选…

一款轻量级、高性能、功能强大的内网穿透代理服务器

简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发,可支持任何tcp、udp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还支持内网htt…

如何保证MySQL和Redis中的数据一致性?

文章目录 前言一、缓存案例1.1 缓存常见用法1.2 缓存不一致产生的原因 二、解决方案2.1 先删除缓存,再更新数据库2.2 先更新数据库,删除缓存2.3 只更新缓存,由缓存自己同步更新数据库2.4 只更新缓存,由缓存自己异步更新数据库2.5 …

Fink CDC数据同步(一)环境部署

1 背景介绍 Apache Flink 是一个框架和分布式处理引擎,用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。 Flink CDC 是 Apache Flink 的一组源连接器,基于数据库日志的…

K8S部署Harbor(三部曲之三:使用)

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…