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

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

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

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

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

kotlin 复制代码
interface Clothe {
    fun feature()  //衣服的基础功能是蔽体
}

open class TShirt : Clothe {
    open override fun feature() {
        println("T恤轻薄透气,适合夏天")
    }
}

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

kotlin 复制代码
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个子类了:

kotlin 复制代码
class SnowTShirt(): TShirt()  //防雪
class SnowWaterproofTShirt(): TShirt()  //防水、防雪
class SnowSunscreenTShirt(): TShirt()   //防晒、防雪
class SnowSunscreenWaterproofTShirt(): TShirt() //防晒、防水、防雪

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

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

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

kotlin 复制代码
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()

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

kotlin 复制代码
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 方法:

kotlin 复制代码
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. 装饰器类和原始类都继承同样的父类,这样可以嵌套的增加其他装饰器。

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

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

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

总结

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

相关推荐
可乐加.糖8 分钟前
一篇关于Netty相关的梳理总结
java·后端·网络协议·netty·信息与通信
s91236010110 分钟前
rust 同时处理多个异步任务
java·数据库·rust
9号达人11 分钟前
java9新特性详解与实践
java·后端·面试
cg501715 分钟前
Spring Boot 的配置文件
java·linux·spring boot
啊喜拔牙22 分钟前
1. hadoop 集群的常用命令
java·大数据·开发语言·python·scala
anlogic1 小时前
Java基础 4.3
java·开发语言
非ban必选1 小时前
spring-ai-alibaba第七章阿里dashscope集成RedisChatMemory实现对话记忆
java·后端·spring
A旧城以西1 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
杉之2 小时前
选择排序笔记
java·算法·排序算法
Naive_72 小时前
蓝桥杯准备(前缀和差分)
java·职场和发展·蓝桥杯