设计模式之【装饰者模式】,实现“穿衣打扮”自由原来这么简单

news2024/9/21 20:33:23

文章目录

  • 一、什么是装饰者模式
    • 1、装饰者模式原理
    • 2、装饰者模式四大角色
    • 3、代理、桥接、装饰器、适配器 4 种设计模式的区别
    • 4、装饰者模式的应用场景
    • 5、装饰者模式和代理模式的对比
    • 6、装饰者模式优缺点
    • 7、抽象装饰器(Decorator)是必需的吗
  • 二、实例1-煎饼
    • 使用装饰者模式优化代码
  • 三、实例2-日志
  • 四、JDK中IO流对装饰者模式的使用
  • 参考资料

一、什么是装饰者模式

装饰者模式(Decorator Pattern),也称为包装模式(Wrapper Pattern)、装饰器模式,是指在不改变原有对象的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。

在 GoF 的《设计模式》一书中,对装饰者模式是这样理解的:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式相比生成子类更灵活。

装饰者模式的核心是扩展功能。使用装饰者模式可以透明且动态地扩展类的功能。

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

1、装饰者模式原理

装饰者模式主要用于透明且动态地扩展类的功能。

其实现原理为:让装饰器实现被包装类(ConcreteComponent)和相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有个功能上添加新功能了。而且由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一层的对最底层被包装类进行功能扩展了。

2、装饰者模式四大角色

从UML类图中,我们可以看到,装饰者模式主要包含四种角色:
在这里插入图片描述
抽象组件(Component):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被装饰对象的行为;

具体组件(ConcreteComponent):实现/继承Component的一个具体对象,也即 被装饰对象;

抽象装饰器(Decorator):通用的装饰ConcreteComponent的装饰器,其内部必然有一个属性指向Component抽象组件;其实现一半是一个抽象类,主要是为了让其子类按照其构造形式传入一个Component抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许多装饰器,那么我们可以直接忽略该类,而直接实现一个具体装饰器(ConcreteDecorator)即可);

具体装饰器(ConcreteDecorator):Decorator的具体实现类,理论上,每个ConcreteDecorator都扩展了Component对象的一种功能。

装饰者模式角色分配符合设计模式里氏替换原则、依赖倒置原则,从而使得其具备很强的扩展性,最终满足开闭原则。

3、代理、桥接、装饰器、适配器 4 种设计模式的区别

代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

4、装饰者模式的应用场景

装饰者模式在我们生活中应用很广,比如说煎饼加鸡蛋加肠,给蛋糕加水果,给房子装修等等,为对象扩展一些额外的职责。装饰器在代码程序中适用于以下场景:
(1)用于扩展一个类的功能或给一个类添加附加职责。
(2)动态的给一个对象添加功能,这些功能可以再动态的撤销。
(3)需要为一批的兄弟类进行改装或加装功能。

5、装饰者模式和代理模式的对比

我们看一下代理模式的UML类图:
在这里插入图片描述
从代理模式的UML类图和通用代码实现上看,代理模式与装饰者模式几乎一模一样。代理模式的Subject对应装饰者模式的Component,代理模式的RealSubject对应装饰者模式的ConcreteComponent,代理模式的Proxy对应装饰器模式的Decorator。确实,从代码实现上看,代理模式的确与装饰者模式是一样的(其实装饰者模式就是代理模式的一个特殊应用),但是这两种设计模式所面向的功能扩展面是不一样的:

装饰者模式强调自身功能的扩展。Decorator所做的就是增强ConcreteComponent的功能(也有可能减弱功能),主体对象为ConcreteComponent,着重类功能的变化;

代理模式强调对代理过程的控制。Proxy完全掌握对RealSubject的访问控制,因此,Proxy可以决定对RealSubject进行功能扩展,功能缩减甚至功能散失(不调用RealSubject方法),主体对象为Proxy;

简单来讲,假设现在小明想租房,那么势必会有一些事务发生:房源搜索、联系房东谈价格……
假设我们按照代理模式进行思考,那么小明只需找到一个房产中介,让他去 干房源搜索,联系房东谈价格这些事情,小明只需等待通知然后付点中介费就行了;
而如果采用装饰器模式进行思考,因为装饰器模式强调的是自身功能扩展,也就是说,如果要找房子,小明自身就要增加房源搜索能力扩展,联系房东谈价格能力扩展,通过相应的装饰器,提升自身能力,一个人做满所有的事情。

6、装饰者模式优缺点

优点:
1.装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即插即用。
2.通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3.装饰器完全遵守开闭原则。

缺点:
1.会出现更多的代码,更多的类,增加程序复杂性。
2.动态装饰时,多层装饰时会更复杂。

7、抽象装饰器(Decorator)是必需的吗

不是必须的,抽象装饰器的本质就是将附加功能抽离出来,简化原有逻辑,可以根据业务模型,可选择的忽略抽象装饰器。

二、实例1-煎饼

很多小伙伴喜欢逛街吃小吃,此时我们有这样一个需求:一个煎饼摊,煎饼可以加鸡蛋、加香肠,计算最终的价格,我们如何实现?

创建一个煎饼类:

public class Battercake {

    protected String getMsg(){ return "煎饼";}

    public int getPrice(){ return 5;}
}

加一个鸡蛋:

public class BattercakeWithEgg extends Battercake {

    protected String getMsg(){ return super.getMsg() + "+1个鸡蛋";}

    public int getPrice(){ return super.getPrice() + 1;}

}

即加鸡蛋又加香肠:

public class BattercakeWithEggAndSauage extends BattercakeWithEgg {

    protected String getMsg(){ return super.getMsg() + "+1根香肠";}

    public int getPrice(){ return super.getPrice() + 2;}

}

此时的类继承关系如下:
在这里插入图片描述
测试类:

public class Test {

    public static void main(String[] args) {
        Battercake battercake = new Battercake();
        System.out.println(battercake.getMsg() + ",总价:" + battercake.getPrice());

        BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
        System.out.println(battercakeWithEgg.getMsg() + ",总价:" + battercakeWithEgg.getPrice());

        BattercakeWithEggAndSauage battercakeWithEggAndSauage = new BattercakeWithEggAndSauage();
        System.out.println(battercakeWithEggAndSauage.getMsg() + ",总价:" + battercakeWithEggAndSauage.getPrice());
    }

}

运行结果是没有问题的,但是,如果客户想要只加香肠,或者加辣条等等,那么我们的工作量无疑是很大的。

此时,用装饰器模式可以完美解决这个问题。

使用装饰者模式优化代码

// 煎饼抽象类,或者接口
public abstract class Battercake {

    protected abstract String getMsg();

    protected abstract int getPrice();
}
// 煎饼基础套餐(什么也不包装,什么也不加)
public class BaseBattercake extends Battercake{

    protected String getMsg(){ return "煎饼";}

    public int getPrice(){ return 5;}

}

此时,需要创建一个扩展套餐的抽象装饰器:

public class BattercakeDecorator extends Battercake{

    private Battercake battercake;

    public BattercakeDecorator(Battercake battercake) {
        this.battercake = battercake;
    }

    protected String getMsg(){ return this.battercake.getMsg();}

    public int getPrice(){ return this.battercake.getPrice();}
}
// 鸡蛋装饰器
public class EggDecorator extends BattercakeDecorator{

    public EggDecorator(Battercake battercake) {
        super(battercake);
    }
    protected String getMsg(){ return super.getMsg() + "1个鸡蛋";}

    public int getPrice(){ return super.getPrice() + 1;}
}

// 香肠装饰器
public class SauageDecorator extends BattercakeDecorator{

    public SauageDecorator(Battercake battercake) {
        super(battercake);
    }
    protected String getMsg(){ return super.getMsg() + "1根香肠";}

    public int getPrice(){ return super.getPrice() + 2;}
}
// 测试类
public class Test {
    public static void main(String[] args) {
        Battercake battercake;
		// 基础套餐
        battercake = new BaseBattercake();
		// 加鸡蛋
        battercake = new EggDecorator(battercake);
		// 再加一个鸡蛋
        battercake = new EggDecorator(battercake);
		// 加香肠
        battercake = new SauageDecorator(battercake);

        System.out.println(battercake.getMsg() + ",总价" + battercake.getPrice());

    }
}

在这里插入图片描述

三、实例2-日志

假如说我们现有的框架是使用Slf4j+log4j2 实现的,但是现有的日志体系打印出的结果是一段没有任何格式的字符串:

Logger logger = LoggerFactory.getLogger(clazz);
logger.info("测试内容");

我们想将打印的结果转成json格式,就需要采用装饰器模式了。

定义装饰器类,实现顶层Logger接口:

public class LoggerDecorator implements Logger {

    protected Logger logger;

    public LoggerDecorator(Logger logger) {
        this.logger = logger;
    }

    public void info(String s) {

    }
    // 省略其他实现
}

创建装饰器实现类:

public class JsonLogger extends LoggerDecorator {
    public JsonLogger(Logger logger) {
        super(logger);
    }

    @Override
    public void info(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }

    @Override
    public void error(String s) {
        JSONObject result = newJsonObject();
        result.put("message",s);
        logger.info(result.toString());
    }
    @Override
    public void error(String s, Throwable e){
        JSONObject result = newJsonObject();
        result.put("exception",e.getClass().getName());
        String trace = Arrays.toString(e.getStackTrace());
        result.put("starckTrace",trace);
        logger.info(result.toString());
    }

    private JSONObject newJsonObject(){
        return new JSONObject();
    }
}

在JsonLogger中,对于Logger的各种接口,我们都用JsonObject进行封装,最终还是调用logger.info,只是这个字符串被我们装饰过

定义一个 工厂类,方便使用:

public class JsonLoggerFactory {

    public static JsonLogger getLogger(Class clazz){
        Logger logger = LoggerFactory.getLogger(clazz);
        return new JsonLogger(logger);
    }
}

public class Test {
    private static final Logger logger = JsonLoggerFactory.getLogger(Test.class);

    public static void main(String[] args) {
        logger.error("系统错误");

        try {
            int i = 1/0;

        } catch (Exception e) {
            logger.error("异常", e);
        }

    }
}

在这里插入图片描述

四、JDK中IO流对装饰者模式的使用

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子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

参考资料

http://www.uml.org.cn/sjms/202105262.asp

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

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

相关文章

上门推拿app开发软件|上门推拿o2o源码|上门推拿小程序

随着社会的发展,人们越来越注重身体健康,推拿按摩已经成为了不少人减轻身体疲劳、缓解压力的重要方式。但是,传统的推拿按摩前往店铺消费时间成本高、实现复杂,为此同城预约上门推拿小程序成为了时代的产物。   1. 市场需求大&a…

【机器学习】线性回归模型详解

PS:本文有一定阅读门槛,如果有不明白的地方欢迎评论询问! 1 模型概述 接下来我们将要学习我们的第一个模型——线性回归。比如说我需要根据数据预测某个面积的房子可以卖多少钱 接下来我们会用到以下符号: m:训练样本数量x:输…

软考A计划-重点考点-专题七(软件工程)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分享&am…

当今自然语言处理领域中的成功之路:Transformer模型

当今自然语言处理领域中最重要和最成功的模型之一是Transformer模型。它是一种基于自注意力机制的神经网络模型,最初由Google公司的研究人员提出,并被广泛应用于机器翻译、文本生成、情感分析等任务中。 Transformer模型之所以被广泛使用,是因…

AI 将完全取代前端开发吗?

注:今晨,我浏览 Medium,看到了篇颇为标题党的文章,于是我就将它抛给了 ChatGPT。本篇文章全部由 ChatGPT 所写。同时,我也请 ChatGPT 分享了它对此的观点。 最近,我的同事向我讲述了他与他老板的一次谈话。…

Golang每日一练(leetDay0061) 表列序号、阶乘后的零

目录 171. Excel 表列序号 Excel Sheet Column Number 🌟 172. 阶乘后的零 Factorial Trailing Zeroes 🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练…

ImageBind 多模态文本、图像、音频 Embedding同一个共享空间

参考: https://github.com/facebookresearch/ImageBind ImageBind learns a joint embedding across six different modalities - images, text, audio, depth, thermal, and IMU data ImageBind 多个模态共享同一个空间Embedding,这样可以通过一个模态…

【虾皮shopee来赞达lazada】各区域商品详情API接口返回值说明

虾皮shopee电商数据(来赞达lazada同理) 虾皮(shopee)是一个亚洲区域的电商平台,主要在东南亚地区提供电商服务。虾皮提供了丰富的电商数据,包括商品数据、订单数据、会员数据、评价数据等。以下是具体的介绍: 商品数据…

SpringBoot ( 二 ) 配置Controller

2.Controller处理请求 由于 在建立 SpringBoot项目时选择的 Web > Spring Web , Maven会导入 SpringMVC 框架 依赖, 做为 Web处理框架 在 SpringMVC框架中 , 通过 Controller类中的方法 来处理请求, 产生响应 在方法中 要解决以下问题 标识方法转页接收请求时传递信息封装响…

生物识别技术的安全挑战

背景 如今,生物识别技术已成为最流行的趋势之一,该趋势旨在通过更具弹性的身份验证机制来提高安全性并阻止网络攻击。目前,全球不仅面临着越来越多的跨设备和系统的网络威胁,冠状病毒大流行还改变了消费者和企业对数字和物理风险…

ChatGPT会如何影响我们的工作生活和人力资源需求

ChatGPT,这几天体验了一下,确实是非常震撼。 一方面是因为它的回答确实相当好,自带一点框架逻辑,有上下文理解能力,可以追问,有情商。虽然很多时候都是一些正确的废话 它还有媲美一个普通大学生的信息整合…

PCIe热插拔机制(详细)总结-PCIe专题知识(五)

目录 前言一、概述二、原理详解2.1 热插拔原理总结2.2 热插拔软硬件要求 三、其他相关知识链接1、PCIe物理层总结-PCIE专题知识(一)2、PCIe数据链路层图文总结-PCIe专题知识(二)3、PCIe物理层链路训练和初始化总结-PCIe专题知识&a…

macOS Ventura 13.4 RC(22F62)发布

系统介绍 5 月 10 日消息,苹果今日向 Mac 电脑用户推送了 macOS 13.4 RC 更新(内部版本号:22F62),本次更新距离上次发布隔了 49 天。 macOS Ventura 带来了台前调度、连续互通相机、FaceTime 通话接力等功能。其中&a…

工程化:vite4和vue3里面的命令式loading的封装及使用

用习惯了vue的组件使用方式,转到vue3里面发现没有了vue的原型,不能全局挂载方法了,我们要使用命令式调用组件该怎么做, 效果展示 代码演练 1.组件结构 2.基础的组件模板loading.vue <template><sectionclass="full-loading":class

Java 诊断神器:Arthas

Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类加载信…

SpringBoot【开发实用篇】---- 测试

SpringBoot【开发实用篇】---- 测试 1. 加载测试专用属性2. 加载测试专用配置3. Web环境模拟测试4. 数据层测试回滚5. 测试数据用例设定 说完bean配置相关的内容&#xff0c;下面要对前面讲过的一个知识做加强了&#xff0c;测试。测试是保障程序正确性的唯一屏障&#xff0c;在…

ubuntu重启ssh服务

一、开启ssh服务首先需要安装打开ssh服务的库&#xff1a; sudo apt-get install openssh-server 二、检查当前的ssh开启情况&#xff1a; ps -e |grep ssh 三、如果有sshd&#xff0c;则ssh-server已经启动&#xff1b;若仅有agent&#xff0c;则尚未启动&#xff1b; 开启ssh…

宝塔面板Nginx防火墙安装

宝塔防火墙作用 要让你的网站保持持久的安全和稳定&#xff0c;开启网站防火墙是必不可少的一步。别等到被攻击的时候再去补救&#xff0c;那个时候某种程度来讲&#xff0c;已经晚了。安装和开启防火墙&#xff0c;就是防患于未然。未雨绸缪。最大程度保护好你的云服务器和网站…

JAVAWeb11-服务器渲染技术 -JSP-03-JSTL(会使用)

1. JSTL 标签库介绍 JSTL 标签库 是指 JSP Standard Tag Library &#xff1a;JSP 标准标签库EL 表达式是为了替换 jsp 中的表达式脚本&#xff0c;JSTL 是为了替换代码脚本。这样 jsp 页面变得更佳简洁JSTL 由五个标签库组成 使用 JSTL&#xff0c;需要导入相关的 jar 包 2…

一文读懂:客户管理系统平台是什么?有什么作用?

“客户管理系统平台是什么&#xff1f;” “客户管理系统平台有什么作用&#xff1f;在哪里可以应用&#xff1f;怎么用&#xff1f;” 经常可以听到企业内部关于客户管理系统平台的这些问题&#xff0c;本文将会为您一一解答&#xff1a; 一、客户管理系统平台是什么 顾名…