重修设计模式-结构型-装饰器模式
在不修改原有类代码的情况下,通过创建包装类(即装饰器)给对象添加一些额外的功能。
装饰器模式(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 中,输入输出流( InputStream
、OutputStream
)就是通过装饰器模式进行扩展。例如,BufferedInputStream
和 BufferedOutputStream
就是对 InputStream
和 OutputStream
的装饰,它们通过添加缓冲区来提高读写效率;DataInputStream
和 DataOutputStream
支持按照基本数据类型(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 中 DataInputStream
和 BufferedInputStream
也存在同样的问题,所以为了避免代码重复,Java IO 又抽象出了一个装饰器父类 FilterInputStream
,在其内部实现了所有方法并做委托操作。这样,装饰器类只需要实现它需要增强的方法就可以了,其他方法由装饰器父类默认委托给传入的 InputStream
对象,FilterInputStream
源码如下:
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。
装饰器模式有两个特点:
- 装饰器类包裹了所要装饰的对象实例,以便在原有功能上增加新的功能。
- 装饰器类和原始类都继承同样的父类,这样可以嵌套的增加其他装饰器。
装饰器模式和代理模式对比:
装饰器模式和静态代理实现非常相似,区别主要有以下几点:
- 代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
- 代理模式目标对象往往不直接对外提供服务,而是由代理类全权代理;装饰器模式目标对象仍然可以自行对外提供服务,装饰器只起增强和辅助作用。
- 代理模式希望对原始类对象有访问控制,隐藏对象的内部细节;装饰器模式用的就是原始类对象的功能,对额外的装饰器功能选择性添加。
总结
装饰器模式主要的作用是给原始类添加增强功能,解决通过继承方式增加功能时导致的类爆炸问题,通过组合来代替继承。此外,装饰器模式还需要有共同父类,方便对原始类嵌套的使用多个装饰器。