写在前言

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。

上面对应着一条重要的设计原则-​​“开闭原则”,类应该对扩展开发,对修改关闭。​

那么有哪些设计模式遵从这一原则呢?其中一个就是​​“装饰者模式”​​。

【1】装饰者模式

装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

几个特点

  • 装饰者和被装饰者对象有相同的超类型;
  • 你可以使用一个或多个装饰者包装一个对象;
  • 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的)场合,可以用装饰过的对象代替它;
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的;
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

组合与继承

继承是给一个类添加行为的比较有效的途径。通过使用继承,可以使得子类在拥有自身方法的同时,还可以拥有父类的方法。但是使用继承是静态的,在编译的时候就已经决定了子类的行为,我们不便于控制增加行为的方式和时机。

组合即将一个对象嵌入到另一个对象中,由另一个对象来决定是否引用该对象来扩展自己的行为。这是一种动态的方式,我们可以在应用程序中动态的控制。

与继承相比,组合关系的优势就在于不会破坏类的封装性,且具有较好的松耦合性,可以使系统更加容易维护。但是它的缺点就在于要创建比继承更多的对象。

UML类图

认真学习设计模式之装饰者模式(Decorator Pattern)_装饰者模式
装饰者模式角色:

Component:定义一个对象接口,可以给这些对象动态添加职责。真实对象和装饰者对象有相同的接口,这样客户端不用知道内部有装饰者对象(Decorator)存在的,还是以之前处理真实对象的相同方式来和装饰者对象交互。

ConcreteComponent:是定义了一个具体的对象(例如:人),也可以给这个对象添加一些其他职责。

Decorator:装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对Component来说,是无需知道Decorator存在的。

ConcreteDecorator:就是具体的装饰对象了(衣服,鞋子…),它起到了给Component添加职责的功能。


【2】代码实现

① 定义一个Component对象接口(MyCar),汽车在跑

public interface MyCar {
void run(); //汽车行驶
}

② 定义一个具体真实的对象ConcreteComponent

这里是Car,就是具体的汽车,未装饰的汽车。

public class Car implements MyCar{
@Override
public void run() {
System.out.println("汽车在跑");
}
}

③ 装饰抽象类Decorator

SuperCar这里需要持有一个真实对象的引用,也就是Car对象

public class SuperCar implements MyCar {
protected MyCar car;//持有一个真实对象的引用

@Override
public void run() {
car.run();//这里调用真实对象的移动方法
}
//构造的时候传参
public SuperCar(MyCar car) {
super();
this.car = car;
}
}

④ 开始实现具体的装饰对象ConcreteDecorator

public class FlayCar extends SuperCar {
public FlayCar(MyCar car) {
super(car);
}
//这里就是新增的功能
public void flay(){
System.out.println("---天上飞");
}
@Override
public void run() {
super.run();
flay();//在原有移动的基础上,装饰了一个fly的功能
}
}

public class WaterCar extends SuperCar {
public WaterCar(MyCar car) {
super(car);
}
//这里就是新增的功能
public void swim(){
System.out.println("---水里游");
}
@Override
public void run() {
super.run();
swim();//在原有移动的基础上,装饰了一个swim的功能
}
}

⑤ 测试代码

public class CustomerTest {
public static void main(String[] args) {
Car car = new Car();
car.run();//这里打印未增加新功能的时候:汽车移动

System.out.println("--------增加飞行功能-------");
FlayCar flyCar = new FlayCar(car);//将真实对象传入装饰对象中
flyCar.run();//这里就是增加了飞行后的装饰

System.out.println("--------增加潜水功能-------");
WaterCar waterCar = new WaterCar(car);//将真实对象传入装饰对象中
waterCar.run();//这里就是增加了潜水功能后的装饰

}
}

认真学习设计模式之装饰者模式(Decorator Pattern)_装饰模式_02
代码的uml类图结构如下:
认真学习设计模式之装饰者模式(Decorator Pattern)_包装器模式_03


【3】真实世界的装饰者:Java I/O

Java中I/O的相关类中许多类都是装饰者。下面是一个典型的对象集合,用装饰者来将功能结合起来,以读取文件数据:
认真学习设计模式之装饰者模式(Decorator Pattern)_装饰者模式_04
​​​BufferedInputStream​​​及​​LineNumberInputStream​​​都扩展自​​FilterInputStream​​​,而​​FilterInputStream​​是一个抽象的装饰类。

FilterInputStream类的主要属性与构造方法如下:

public class FilterInputStream extends InputStream {

protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
//...
}

装饰j a v a . i o类
认真学习设计模式之装饰者模式(Decorator Pattern)_装饰者模式_05
你会发现“输出”流的设计方式也是一样的。你可能还会发现Reader/Writer流(作为基于字符数据的输入输出)和输入流/输出流的类相当类似(虽然有一些小差异和不一致之处,但是相当雷同,所以你应该可以了解这些类)。

但是JavaAI/O也引出装饰者模式的一个“缺点”:利用装饰者模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。


编写自己的J a v a I / 0装饰者

编写一个装饰者,把输入流内的所有大写字符转成小写。举例:当读取​​“I k n o w t h e D e c o r a t o r P a t t e r n t h e r e f o r e I R U L E !”​​​,装饰者会将它转成​​“i k n o w t h e d e c o r a t o r p a t t e r n t h e r e f o r e i r u l e !”​

public class LowerCaseInputStream extends FilterInputStream {
public LowerCaseInputStream(InputStream in) {
super(in);
}
/**
现在,必须实现两个read()方法,一个针对字节,一个针对字节数组,把每个是大写字符
的字节(每个代表一个字符)转成小写。
*/
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toLowerCase((char)c));
}
public int read(byte[] b, int offset, int len) throws IOException {
int result = super.read(b, offset, len);
for (int i = offset; i < offset+result; i++) {
b[i] = (byte)Character.toLowerCase((char)b[i]);
}
return result;
}
}

测试如下:

public class InputTest {
public static void main(String[] args) throws IOException {
int c;
try {
InputStream in =new LowerCaseInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")));
while((c = in.read()) >= 0) {
System.out.print((char)c);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行结果如下:
认真学习设计模式之装饰者模式(Decorator Pattern)_包装器模式_06


【4】总结

​装饰模式(Decorator)也叫包装器模式(Wrapper)​​。装饰模式降低系统的耦合度,可以动态的增加或删除对象的职责,并使得需要装饰的具体构建类和具体装饰类可以独立变化以便增加新的具体构建类和具体装饰类。

优点:

  • 扩展功能强,相比继承来说更灵活。继承的话会导致子类个数增加。而装饰者模式不会出现这种情况。
  • 可以对一个对象进行多次装饰,创造出不同行为的组合,得到功能更加强大的对象。
  • 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的构件子类和具体装饰类。

不足:

  • 产生很多小对象,大量小对象会占据内存。一定程度上影响了性能。
  • 如果过度使用,会让程序变得很复杂。
  • 装饰模式易于出错,调试排查比较麻烦。

开发中应用的场景:

  • IO中输入流和输出流
  • Swing包中图形界面构件功能
  • Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,增强了request对象的功能。
  • Struts2中,request,response,session对象的处理。

装饰模式和桥接模式的区别:

两个模式都是为了解决过多子类对象的问题,桥接模式是对象自身有过多的维度,造成过多的子类。而让维度分类后在搭建一个桥梁来联系起来。而装饰模式是解决在增加新功能的时候产生多个类的问题。