Java 设计模式——装饰者模式

news2024/9/23 9:36:01

目录

  • 1.概述
  • 2.结构
  • 3.案例实现
    • 3.1.抽象组件
    • 3.2.具体组件
    • 3.3.抽象装饰
    • 3.4.具体装饰
    • 3.5.测试
  • 4.优缺点
  • 5.使用场景
  • 6.JDK 源码解析——BufferedWriter
  • 7.装饰者模式和静态代理的比较

1.概述

(1)我们先来看一个快餐店的例子:快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。而使用继承的方式存在的问题:

  • 扩展性不好:如果要再加一种配料(火腿肠),我们就会发现需要给 FriedRiceFriedNoodles 分别定义一个子类。如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子类。
  • 会产生过多的子类
    在这里插入图片描述

(2)装饰者模式 (Decorator Pattern) 是一种结构型设计模式,它能在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)。

2.结构

装饰者模式中的角色如下:

  • 抽象组件 (Component) 角色:定义一个抽象接口以规范准备接收附加责任的对象。
  • 具体组件 (Concrete Component) 角色:实现抽象组件,通过装饰角色为其添加一些职责。
  • 抽象装饰 (Decorator) 角色:继承或实现抽象构件,并包含具体组件的实例,可以通过其子类扩展具体组件的功能。
  • 具体装饰 (Concrete Decorator) 角色:实现抽象装饰的相关方法,并给具体组件对象添加附加的责任。

3.案例实现

我们使用装饰者模式对快餐店案例进行改进,体会装饰者模式的精髓。其类图如下:

在这里插入图片描述

3.1.抽象组件

FastFood.java

//快餐类
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 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;
    }
    
    public abstract float cost();
}

3.2.具体组件

FriedRice.java

//炒饭
public class FriedRice extends FastFood{
    
    public FriedRice(){
        super(10, "炒饭");
    }
    
    @Override
    public float cost() {
        return getPrice();
    }
}

FriedNoodles.java

//炒面
public class FriedNoodles extends FastFood{
    
    public FriedNoodles(){
        super(12,"炒面");
    }
    
    @Override
    public float cost() {
        return getPrice();
    }
}

3.3.抽象装饰

Garnish.java

//装饰者类
public abstract class Garnish extends FastFood{
    //声明快餐类的变量
    private FastFood fastFood;
    
    public FastFood getFastFood() {
        return fastFood;
    }
    
    public void setFastFood(FastFood fastFood) {
        this.fastFood = fastFood;
    }
    
    public Garnish(FastFood fastFood, float price, String desc) {
        super(price, desc);
        this.fastFood = fastFood;
    }
}

3.4.具体装饰

Egg.java

//鸡蛋类
public class Egg extends Garnish{
    
    public Egg(FastFood fastFood){
        super(fastFood, 1, "鸡蛋");
    }
    
    @Override
    public float cost() {
        //计算价格,鸡蛋价格 + 快餐价格
        return getPrice() + getFastFood().cost();
    }
    
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

Bacon.java

//培根类
public class Bacon extends Garnish{
    
    public Bacon(FastFood fastFood){
        super(fastFood, 2, "培根");
    }
    
    @Override
    public float cost() {
        //计算价格,培根价格 + 快餐价格
        return getPrice() + getFastFood().cost();
    }
    
    @Override
    public String getDesc() {
        return super.getDesc() + getFastFood().getDesc();
    }
}

3.5.测试

Client.java

public class Client {
    public static void main(String[] args) {
        //点一份炒饭
        FastFood food = new FriedRice();
    
        System.out.println(food.getDesc() + "  " + food.cost() + " 元");
    
        System.out.println("===============");
    
        //在上面的炒饭中加一个鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + " 元");
    
        System.out.println("================");
        //再加一个鸡蛋
        food = new Egg(food);
        System.out.println(food.getDesc() + "  " + food.cost() + " 元");
    
        System.out.println("================");
        food = new Bacon(food);
        System.out.println(food.getDesc() + "  " + food.cost() + " 元");
    }
}

输出结果如下:

炒饭  10.0===============
鸡蛋炒饭  11.0================
鸡蛋鸡蛋炒饭  12.0================
培根鸡蛋鸡蛋炒饭  14.0

在上述例子中使用装饰者模式至少有以下优点:

  • 饰者模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰者模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附加责任,装饰者则是动态的附加责任。
  • 装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

4.优缺点

(1)装饰者模式的优点和缺点如下所示:

  • 优点
    • 动态扩展:装饰者模式允许在运行时动态给对象添加新的功能,而无需修改其原始类或接口。通过装饰器的层层包装,可以灵活地组合各个功能模块,实现不同组合的功能扩展。
    • 开闭原则:装饰者模式遵循开闭原则,允许向系统中添加新的装饰者,而无需修改现有代码。这使得系统更加灵活,易于扩展和维护。
    • 单一职责原则:装饰者模式通过将功能细分到不同的装饰器中,使得每个装饰器只关注特定的责任或功能。这可以遵循单一职责原则,使类的设计更加清晰和可维护。
  • 缺点
    • 复杂性增加:使用装饰者模式会引入许多小的装饰器类,这可能增加类的数量和复杂性。当装饰者的层级过多时,代码可读性和理解难度会增加,维护也变得复杂。
    • 注重细节:装饰者模式强调在对象之间细粒度的功能组合,这在某些情况下可能会添加不必要的复杂性。对于简单的场景,使用装饰者模式可能会显得过于繁琐。
    • 初始对象需求:装饰者模式要求初始对象实现共同的接口或继承共同的抽象类,以便于装饰器的添加和替换。如果原始对象不符合这些要求,则在引入装饰者时需要进行额外的改造。

(2)总体而言,装饰者模式提供了一种灵活的方式来扩展对象的功能,符合开闭原则和单一职责原则。然而,它也可能引入复杂性并要求对代码进行更多的设计和精心安排。在实际应用中,需要根据具体场景来评估使用装饰者模式的利弊,权衡灵活性和复杂性之间的关系。

5.使用场景

(1)装饰者模式适用于以下场景:

  • 动态添加功能:当需要在运行时动态地为对象添加额外的功能时,可以使用装饰者模式。它提供了一种灵活的方式来组合各个功能模块,而不需要修改原始对象的代码。
  • 避免类爆炸:当类的数量可能会爆炸增长时,可以使用装饰者模式来避免创建大量的子类。通过装饰者模式,可以将各个功能划分到不同的装饰器类中,而不是创建多个子类来实现不同组合的功能。
  • 单一职责原则:当一个类承担了多个责任或功能时,可以使用装饰者模式将每个功能划分到不同的装饰器类中。这样做可以遵循单一职责原则,使类的设计更加清晰和可维护。
  • 可撤销的功能:当需要在运行时可以撤销已添加的功能时,装饰者模式提供了一种方便的方式。只需要移除相应的装饰器即可撤销已添加的功能,而不需要修改原始对象的代码。
  • 继承和复合的替代方案:装饰者模式可以替代继承来扩展对象的功能。相比于继承,装饰者模式更加灵活,允许动态地组合各个功能模块,而不受类的继承关系的限制。

(2)总的来说,装饰者模式适用于需要动态地为对象添加功能或对对象的功能进行扩展的场景。它提供了一种灵活的方式来组合各个功能模块,保持接口的一致性,同时符合开闭原则和单一职责原则。常见的应用场景包括:日志记录、缓存、权限验证、事务管理等。

6.JDK 源码解析——BufferedWriter

(1)I/O 流中的包装类使用到了装饰者模式:BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
(2)现以 BufferedWriter 举例来说明,先看看如何使用 BufferedWriter

import java.io.BufferedWriter;
import java.io.FileWriter;

public class Demo { 
    public static void main(String[] args) throws Exception{
        //创建 BufferedWriter 对象
        //创建 FileWriter 对象
        FileWriter fw = new FileWriter("E:\\a.txt");
        BufferedWriter bw = new BufferedWriter(fw);
        //写数据
        bw.write("hello Buffered");
        bw.close();
    }
}

使用起来感觉确实像是装饰者模式,接下来看它们的结构:
在这里插入图片描述
小结:BufferedWriter 使用装饰者模式对 Writer 子实现类进行了增强,添加了缓冲区,提高了写数据的效率。

7.装饰者模式和静态代理的比较

(1)装饰者模式和静态代理模式有一些相同点和不同点,可以总结如下:

  • 相同点
    • 都可以在不修改原始对象的情况下,为其添加额外的功能。
    • 都可以通过增加中间层来控制对原始对象的访问,并在访问前后执行一些额外的操作。
  • 不同点
    • 关注点不同
      • 装饰者模式关注在原始对象的基础上动态地添加新的功能。
      • 静态代理模式关注对原始对象的代理和控制。
    • 对象关系不同
      • 装饰者模式中,装饰者和被装饰者是共同实现某个接口或继承某个类的对象。装饰者通过持有被装饰者的引用,并在运行时动态地添加功能。
      • 静态代理模式中,代理对象持有被代理对象的引用,并在代码中显式地创建和控制被代理对象。
    • 实现方式不同
      • 装饰者模式使用对象组合的方式来实现功能的添加和包装,通过不断地添加装饰者对象,形成装饰器链。
      • 静态代理模式通过创建一个代理类来包装和控制对原始对象的访问,需在代理类中实现对被代理对象的访问控制逻辑。
    • 设计灵活性不同
      • 装饰者模式具有更高的灵活性,可以在运行时动态地添加或删除功能装饰器,实现不同的功能组合。
      • 静态代理模式在编译时被确定,代理类需要事先实现对被代理对象的控制逻辑,灵活性较差。

(2)总体而言,装饰者模式和静态代理模式都是实现对象功能扩展的常见模式,它们通过中间层的方式来添加额外的功能,并控制对原始对象的访问。装饰者模式更加灵活,可以在运行时动态地组合功能装饰器,而静态代理模式在编译时确定代理类和被代理对象的关系。选用哪种模式应基于具体需求和场景来综合考虑。

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

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

相关文章

PCB板框评估及叠层设计

PCB板框评估及叠层设计 板框评估叠层设计 板框评估 首先选中所有器件,点击下图指令 在PCB中画一个矩形框,所有器件将会排列在这个框中 快捷键 E O S 重新定义原点 选择机械1层,快捷键P L 画出大致的板框,板框画好之后&#x…

全网最详细4W字Flink入门笔记(下)

本文已收录至Github,推荐阅读 👉 Java随想录 文章目录 Flink State状态CheckPoint & SavePointCheckPoint原理SavePoint原理 StateBackend状态后端MemoryStateBackendFsStateBackendRocksDBStateBackend集群级配置StateBackend Window滚动窗口&#…

从小白到大神之路之学习运维第61天--------Ansible自动化运维工具(playbook配置文件深入了解)

第三阶段基础 时 间:2023年7月14日 参加人:全班人员 内 容: playbook配置文件 目录 playbook配置文件 一、playbook配置文件概念 修改hosts文件 建立playbook配置文件 yml脚本写法注释: 二、Playbook的核心元素 三、…

【动手学深度学习】--06.暂退法Dropout

文章目录 暂退法(Dropout)1.原理1.1动机1.2无偏差的加入噪音1.3使用丢弃法1.4推理中的丢弃法 2.从零实现Dropout2.1定义模型参数2.2定义模型2.3训练和测试 3.简洁实现 暂退法(Dropout) 学习视频:丢弃法【动手学深度学习v2】 官方笔记:暂退法&#xff0…

vue+canvas图片裁切

vuecanvas图片裁切 头像的裁切、图片的一些处理…… vue:路由router、vuex状态管理 组件效果 组件代码: 实现思路 盒子即一张画布,把选择的图片放到画布中进行裁切,按照canvas的一些语法裁切一部分内容。 有一个固定大小的画布&am…

字节有点飘了,现在阿里员工跳槽字节不受待见

上一篇:对不起,实在扛不住了。。。 字节现在厉害了,不愧为宇宙第一大厂。 阿里员工现在跳槽字节被鄙视,阿里经历竟然是减分项。 据某互联网大厂HR发文透漏:现在阿里跳字节真的不受待见,背景是负加成。 他举…

JVM内存结构——前言

前提 1. 认识JVM,什么是JVM 简单来说,就是java程序的运行环境(java二进制字节码的运行环境) 1.1 JVM (java虚拟机)的好处 : 一次编写,到处运行的机制 (因为java语言是跨…

【SQL应知应会】表分区(三)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习,有基础也有进阶,有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

OSS对象存储后端实现+Vue实现图片上传【基于若依管理系统开发】

文章目录 基本介绍术语介绍图片上传方式介绍普通上传用户直传应用服务器签名后直传 OSS对象存储后端实现maven配置文件配置类ServiceController 图片上传前端图片上传组件api页面使用组件组件效果 基本介绍 术语介绍 Bucket(存储空间):用于…

基于linux下的高并发服务器开发(第一章)- Linux系统IO函数

05 / Linux系统IO函数 (1)man 2 open >>打开一个已经存在的文件 int open(const char *pathname, int flags); 参数: pathname:要打开文件路径 - flags:对文件的操作权限设置还有其他的设置 O_RDONLY,O_WRONLY,O_RDWR 这三个设置是互斥…

#systemverilog# 关于关键字 之 event 了解这些够了(二)->和 ->> 区别

在上一篇文章,我们学习了event 的相关语法和举例,清晰的说明了具体场合的使用规则。今天,我们着重看一下 -> 和 ->> 的区别,进而在以后的工作过程种,能为大家带来一些便利。 我们先看 -> : 【-> 正确的用法】: 【-> 不正确的用法】: 可以看出,触…

BaseDexClassLoader加载类流程源码阅读

安卓10 类图 双亲委派机制 Java层 BaseDexClassLoader 没有loadClass实现&#xff0c;继承的是 ClassLoader 实现 路径 libcore\ojluni\src\main\java\java\lang\ClassLoader.java protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFound…

【Linux】- 任务调度和定时任务

任务调度和定时任务 1 crond 任务调度2 at 定时任务 1 crond 任务调度 crontab 进行 定时任务的设置 任务调度&#xff1a;是指系统在某个时间执行的特定的命令或程序。 任务调度分类&#xff1a;1.系统工作&#xff1a;有些重要的工作必须周而复始地执行。如病毒扫描等 个别…

.Net Framework下面如何生成AOT呢?

前言 有人问&#xff0c;老版本的.net frameworker能不能生成AOT?其实AOT预编译&#xff0c;在.Net FrameWorker1.0里面就有了。它叫做Ngen&#xff0c;只不过当时叫做生成本机映像&#xff0c;实际上还是一个东西&#xff0c;也就是预编译。本篇来看下。原文&#xff1a;.Net…

MySQL数据库多表查询

目录 一、创建表单 二、操作 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 4.从student表中查询计算机系和…

电子表格错误分析 ExcelAnalyzer Platinum v3.4.4 Crack

ExcelAnalyzer是世界上最强大的 Excel 插件&#xff0c;用于检查和更正电子表格。做出更好的决策&#xff0c;并 100% 确信您的电子表格没有错误。 相信您的电子表格数据 只需点击几下即可纠正电子表格错误 易于使用且透明 安全又安全 研究表明&#xff0c;95% 的电子表格包含错…

初识Visual Basic编辑器并建立一段简单的代码(上)

【分享成果&#xff0c;随喜正能量】不要把自己的伤口揭开给别人看&#xff0c;世上多的不是医师&#xff0c;多的是撒盐的人。。 《VBA之Excel应用》&#xff08;10178983&#xff09;是非常经典的&#xff0c;是我推出的第七套教程&#xff0c;定位于初级&#xff0c;目前是…

微服务系列文章 之 nginx日志格式分析以及修改

如何自定义日志格式&#xff0c;就需要修改nginx日志打印格式 一. 打开终端&#xff0c;登录服务器并输入服务器密码 //ssh 用户名服务器ip ssh root192.168.0.132二. 切换到nginx目录 cd /var/log/nginx/ 三. 查看nginx日志 tail -f access.log 日志说明&#xff1a; //…

OpenCV for Python 入坑第二天 :图片处理(1)

上一篇博客我们简单了解了一下如何打开图片、保存图片、创建窗口等等。那么今天我们就来尝试一下完成OpenCV基础操作——图像的处理 文章目录 什么是图像处理图像的展示方法灰度图像介绍彩色图像介绍 图像的像素修改OpenCV的图像存储方式NumPy创建图像彩色图片存储 小结 什么是…

用WinDBG调试DMP文件

把对应工程的pdb文件&#xff0c;放到C盘下的一个文件夹&#xff0c;例如 C:\mysymbols 打开WinDBG&#xff0c;选择File -> Symbol File Path , 输入C:\mysymbols\;SRV*C:\symbols*http://msdl.microsoft.com/download/symbols&#xff0c;然后OK &#xff08;这可以使W…