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

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

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

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

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

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

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

总结

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

相关推荐
duration~34 分钟前
Maven随笔
java·maven
zmgst37 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚3 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐3 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven