重修设计模式-结构型-装饰器模式

embedded/2024/12/23 1:14:20/

重修设计模式-结构型-装饰器模式

在不修改原有类代码的情况下,通过创建包装类(即装饰器)给对象添加一些额外的功能。

装饰器模式(Decorator Pattern)允许在不修改原有类代码的情况下,通过创建一系列包装类来给对象动态地添加一些额外的功能,且增强的功能支持相互叠加。装饰器模式将“组合优于继承”的思想体现的淋漓尽致,当原始类需要增加额外功能时,相比直接生成子类的实现方式,这种模式更为灵活。

举个例子,日常大家都穿着衣服,衣服起着基础的蔽体的功能,首先将衣服和T恤用代码表示出来:

interface Clothe {fun feature()  //衣服的基础功能是蔽体
}open class TShirt : Clothe {open override fun feature() {println("T恤轻薄透气,适合夏天")}
}

但天气是多变的,下雨时需要防水,出太阳了需要防晒,这时需要为衣服增加防水、防晒功能,首先能想到的是用继承的方式去实现:

class WaterproofTShirt(): TShirt() {override fun feature() {super.feature()println("增加防水功能")}
}class SunscreenTShirt(): TShirt() {override fun feature() {super.feature()println("增加防晒功能")}
}class SunscreenWaterproofTShirt(): TShirt() {override fun feature() {super.feature()println("既能防水,又能防晒")}
}//调用时:
val c1 = WaterproofTShirt()  //下雨了,穿防水T恤
c1.feature()
val c2 = SunscreenTShirt()  //出太阳了,穿防晒T恤
c2.feature()

一下就为T恤扩展出三个子类,用于不同的天气。如果需求的扩展到此为止,这样设计是可以接受的,毕竟继承结构还算简单。但如果需求继续增加:下雪了需要防雪,这时不仅需要为T恤增加防雪功能,已有的功能同样也要增加防雪功能,毕竟有雨夹雪的场景,这时需要再扩张出4个子类了:

class SnowTShirt(): TShirt()  //防雪
class SnowWaterproofTShirt(): TShirt()  //防水、防雪
class SnowSunscreenTShirt(): TShirt()   //防晒、防雪
class SnowSunscreenWaterproofTShirt(): TShirt() //防晒、防水、防雪

如果再增加防风功能呢?常穿的衣服不仅有T恤还有毛衣,如果毛衣同样需要支持这些功能呢?

可以看到,用继承的方式去进行功能增强,随着需求不断发展,最终会造成子类爆炸增长的局面。其实映射到真实世界,也并不存在防晒、防风又防水的T恤,大家只会在T恤或毛衣外再套上防晒衣、风衣或雨衣来应对不同天气,这和装饰器模式非常类似。

继续这个例子,下面用装饰器模式去实现:

class Waterproof(var clothes: Clothe): Clothe {override fun feature() {println("套个雨衣,可以防水")clothes.feature()}
}class Sunscreen(var clothes: Clothe) : Clothe {override fun feature() {println("套个防晒衣,可以防晒")clothes.feature()}
}//调用时:
val clothe = TShirt()
val wClothe = Waterproof(clothe)    //下雨了,套上雨衣
val sClothe = Sunscreen(wClothe)    //出太阳了,套上防晒衣
sClothe.feature()

其实就是用了组合的思想,在功能扩展时,只需增加对应的功能类,并包裹真实对象,使用起来也非常灵活。比如在增加防雪功能时,只需增加防雪的装饰类即可:

class SnowClothe(var clothes: Clothe) : Clothe {override fun feature() {println("套个防雪衣,可以防雪?")clothes.feature()}
}//使用时:
val sClothe = SnowClothe(TShirt())
sClothe.feature()

在 Java 中,输入输出流( InputStreamOutputStream)就是通过装饰器模式进行扩展。例如,BufferedInputStreamBufferedOutputStream 就是对 InputStreamOutputStream 的装饰,它们通过添加缓冲区来提高读写效率;DataInputStreamDataOutputStream 支持按照基本数据类型(int、boolean、long 等)来读取数据。虽然 Java IO 的 API 比较杂乱,但只要理解了装饰器模式的思想,相信会很快掌握。

值得注意的一点是,装饰器类中,功能增强可能只涉及共同父类的部分方法重写,但还是需要将所有父类方法都实现一遍,并调用传入对象的对应方法。因为传入对象不一定是原始对象了,可能是包装了其他功能的装饰类对象,不能破坏它们的方法调用结构。举个例子,为上面 Clothe 接口新增个 color 方法:

interface Clothe {fun feature()fun color() {	//Kotlin中接口可以有默认实现,高版本Java也支持了println("衣服颜色")  }
}//装饰类-防雨
class Waterproof(var clothes: Clothe): Clothe {override fun feature() {println("套个雨衣,可以防水")clothes.feature()}override fun color() {println("增加一层透明颜色...")clothes.color()}
}//装饰类-防晒
class Sunscreen(var clothes: Clothe) : Clothe {override fun feature() {println("套个防晒衣,可以防晒")clothes.feature()}override fun color() {clothes.color() //为什么不能用super.color()呢?}
}

结合上面说明,理解一下为什么一定要实现 color 方法,并调用 clothes.color(),而非不实现或调用 super.color()

Java 中 DataInputStreamBufferedInputStream 也存在同样的问题,所以为了避免代码重复,Java IO 又抽象出了一个装饰器父类 FilterInputStream,在其内部实现了所有方法并做委托操作。这样,装饰器类只需要实现它需要增强的方法就可以了,其他方法由装饰器父类默认委托给传入的 InputStream 对象,FilterInputStream 源码如下:

java">public class FilterInputStream extends InputStream {protected volatile InputStream in;protected FilterInputStream(InputStream in) {this.in = in;}public int read() throws IOException {return in.read();}public int read(byte b[]) throws IOException {return read(b, 0, b.length);}public int read(byte b[], int off, int len) throws IOException {return in.read(b, off, len);}public long skip(long n) throws IOException {return in.skip(n);}public int available() throws IOException {return in.available();}public void close() throws IOException {in.close();}public synchronized void mark(int readlimit) {in.mark(readlimit);}public synchronized void reset() throws IOException {in.reset();}public boolean markSupported() {return in.markSupported();}
}

装饰器模式优点是扩展性好,灵活性高,符合开闭原则;缺点是如果装饰器过多,可能造成代码阅读性变差,比如 Java IO 流相关API。

装饰器模式有两个特点:

  1. 装饰器类包裹了所要装饰的对象实例,以便在原有功能上增加新的功能。
  2. 装饰器类和原始类都继承同样的父类,这样可以嵌套的增加其他装饰器。

装饰器模式和代理模式对比:

装饰器模式和静态代理实现非常相似,区别主要有以下几点:

  • 代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
  • 代理模式目标对象往往不直接对外提供服务,而是由代理类全权代理;装饰器模式目标对象仍然可以自行对外提供服务,装饰器只起增强和辅助作用。
  • 代理模式希望对原始类对象有访问控制,隐藏对象的内部细节;装饰器模式用的就是原始类对象的功能,对额外的装饰器功能选择性添加。

总结

装饰器模式主要的作用是给原始类添加增强功能,解决通过继承方式增加功能时导致的类爆炸问题,通过组合来代替继承。此外,装饰器模式还需要有共同父类,方便对原始类嵌套的使用多个装饰器。


http://www.ppmy.cn/embedded/111449.html

相关文章

fpga入门名词(1)

这是第一代FPGA ,在 FPGA(现场可编程门阵列)设计中,LCA(逻辑单元阵列)通常由几个关键组件构成,包括 IOB、CLB 和 Interconnect。以下是这些组件的简要说明: 1. IOB(Input/Output B…

go系列之 cron 表达式

一、简介 robfig/cron 是一个用于 Go 语言的定时任务调度库,它允许开发者以类似于 Unix/Linux 系统中的 cron 守护进程的方式来定义和管理周期性任务。 二、使用教程 c : cron.New() c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour …

JavaWeb项目打包、部署至Tomcat并启动的全程指南(图文详解)

前言 我们想要部署一个javaWeb项目到tomcat上,需要了解一些概念 什么是tomcat? Tomcat 是 Apache 软件基金会(Apache Software Foundation)下的一个开源项目,主要用于实现 Java Servlet、JavaServer Pages(…

Docker概述

Docker能干什么? 虚拟机技术: 资源占用十分多冗余步骤多启动很慢 容器化技术: 不是模拟的一个完整的操作系统,每一个项目可以和自己的运行环境打包形成一个镜像,每个容器之间互相隔离,互不影响。 比较docker和虚拟机的不同 …

数据格式:什么是JSON和XML

JSON和XML都是数据交换的一种格式,用于在不同的系统和应用程序之间传输和存储数据。本文将解释JSON和XML的基础内容,并探讨两者的不同。 一 什么是JSON? 1. JSON(JavaScript Object Notation)即JavaScript对象标记法…

【Oracle】TIMESTAMP类型时间计算时间差

在 Oracle 中,TIMESTAMP 和 TIMESTAMP(6) 的主要区别在于时间精度。TIMESTAMP 默认不包含微秒,而 TIMESTAMP(6) 支持微秒精度(6 位小数秒)。详细说明两者的区别、相互转换,并深入讨论如何计算两个 TIMESTAMP(6) 之间的…

文件批量添加水印和密码合并单元格完整版

这段代码是一个 Java 方法,用于向文件添加水印和密码。您解释一下: 首先,它接受一个 fileAddress 参数,表示文件的地址。 然后,它创建了一个线程安全的列表 fileDatas,用于存储文件数据。 接下来&#xff…

IT 项目管理与需求分析最佳实践

项目管理无处不在,它不仅仅是一个岗位,更是一套科学的工作方法,能够很好地指导我 们的工作与生活。但很多从业者缺少项目管理意识与技巧,为自己的工作增添了许多额外的阻 碍,不仅项目推进不及预期,也让个…