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

目录

  • 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, "炒饭");}@Overridepublic float cost() {return getPrice();}
}

FriedNoodles.java

//炒面
public class FriedNoodles extends FastFood{public FriedNoodles(){super(12,"炒面");}@Overridepublic 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, "鸡蛋");}@Overridepublic float cost() {//计算价格,鸡蛋价格 + 快餐价格return getPrice() + getFastFood().cost();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}

Bacon.java

//培根类
public class Bacon extends Garnish{public Bacon(FastFood fastFood){super(fastFood, 2, "培根");}@Overridepublic float cost() {//计算价格,培根价格 + 快餐价格return getPrice() + getFastFood().cost();}@Overridepublic 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.hqwc.cn/news/26686.html

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

相关文章

【DC-DC】AP5193 DC-DC宽电压LED降压恒流驱动器 LED电源驱动IC

产品 AP5193是一款PWM工作模式,高效率、外围简单、内置功率MOS管,适用于4.5-100V输入的高精度降压LED恒流驱动芯片。最大电流2.5A。AP5193可实现线性调光和PWM调光,线性调光脚有效电压范围0.55-2.6V.AP5193 工作频率可以通过RT 外部电阻编程来设定&…

怎么自学网络安全?遇到问题该怎么解决?

趁着今天下班,我花了几个小时整理了下,非常不易,希望大家可以点赞收藏支持一波,谢谢。 我的经历: 我 19 年毕业,大学专业是物联网工程,我相信很多人在象牙塔里都很迷茫,到了大三大…

Kafka学习笔记(高级篇)

目录 高级功能 高效读写 涉及技术 ZooKeeper 自定义拦截器 监控 延迟消费 一些改进手段 高级功能 高效读写 涉及技术 高吞吐量:Kafka 每秒可以处理数百万消息。这是因为 Kafka 消息的处理是以批处理(Batching)的方式来完成的&…

SpringBoot+Vue的学生选课管理系统

1. 技术栈 前端:Vue ElementUI Axios后端:Spring BootMyBatis Plus Jwt MysqlSwagger 2. 系统设计 该系统主要分为五个模块,分别是:学生管理、教师管理、课程管理、开课表管理以及学生成绩管理 角色分为学生、教师、管理员&…

如何搭建自己的图床(GitHub版)

文章目录 1.图床的概念2.用GitHub创建图床服务器2.1.新建仓库2.2.生成Token令牌2.3.创建img分支和该分支下的img文件夹(可选) 3.使用PicGo软件上传图片3.1 下载PicGo软件3.2配置PicGo3.3用PicGo实现上传 4. Typora实现自动上传5.免费图片网站 前言: 如果没有自己的服…

Xshell配置以及使用教程

目录 一、Xshell介绍 二、安装Xshell 三、使用Xshell连接Linux服务器 一、Xshell介绍 Xshell 分为免费版和专业版,是一款远程连接虚拟机系统的 SSH 客户机软件; Xshell免费版官网下载地址:家庭/学校免费 - NetSarang Websitehttps://www…

半小时漫画计算机

ISBN: 978-7-121-41557-9 作者:刘欣(码农翻身) 绘画:刘奕君 页数:210页 阅读时间:2023-06-03 推荐指数:★★★★★ 以漫画的形式来讲解计算机的基础知识, 主要涉及到CPU、内存、网络…

“layui助力博客管理升级!用增删改查功能打造优质博客体验“

目录 引文1.前置条件2.数据接口2.1 UserDao(CRUD)2.2 R工具类 3.HTML 结构3.1 主界面的HTML3.2 用户的查询所有界面的HTML3.3 新增修改通用的的HTML 4.JavaScript 代码4.1 用户的CRUD javaScript 代码(userManage)4.2 新增修改的javaScript代码(userEdit) 5. 运行截图总结 引文…

kubernetes中特定域名使用自定义DNS服务器出现的解析异常

故障发生背景: 租户反馈生产业务服务连接到中间件的时候,偶尔会有连接失败的情况,然后我们查看对应组件服务正常,手动请求组件服务也显示正常,让租户查看业务服务日志发现报错无法解析对应的域名,我们手动是…

【雕爷学编程】Arduino动手做(163)---大尺寸8x8LED方格屏模块7

37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的&#x…

laravel6.x文档阅读手册

laravel中文文档6.x 目录 一、入门指南 安装 服务器要求 安装 Laravel Laravel 使用 Composer 来管理项目依赖。因此,在使用 Laravel 之前,请确保你的机器已经安装了 Composer。 通过 Laravel 安装器 首先,通过使用 Composer 安装 Lara…

Pod:Kubernetes里最核心的概念

为了解决这样多应用联合运行的问题,同时还要不破坏容器的隔离,就需要在容器外面再建立一个“收纳舱”,让多个容器既保持相对独立,又能够小范围共享网络、存储等资源,而且永远是“绑在一起”的状态。 Pod 的概念也就呼…