【Java设计模式】装饰者设计模式
1. 装饰者模式(Wrapper)概念
动态地给一个对象增加一些额外的职责(增强),增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。在装饰者模式中,为了让系统具有更好的灵活性和可扩展性,我们通常会定义一个抽象装饰类(Decorator),而将具体的装饰类作为它的子类。从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同。
亦称:装饰者模式、装饰器模式、Wrapper、Decorator
- 继承:是在编译时(compile-time)静态地通过对原始类的继承完成
- 装饰者:是在程序运行时(run-time)通过对原始对象动态地“包装”完成
2. 装饰者是什么?
在不必改变原类文件和原类使用的继承的情况下,动态地扩展一个对象的功能。就增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
比如,我们已有的饮料类,扩展了一个新的品种,可以使用装饰者模式,无需用原始的增加子类去继承饮料父类的方式。
3. 装饰者设计模式- 参与者
3.1 组成
-
Component(抽象构件):它是具体构件和抽象装饰类的共同父类【顶级父类】,声明了在具体构件中实现的业务方法,它的引入可以使客户端以一致的方式处理未被装饰的对象以及装饰之后的对象,实现客户端的透明操作。
-
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法)。
-
Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增加职责,但是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,通过该引用可以调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的。
-
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方法,并可以增加新的方法用以扩充对象的行为。
由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。
3.1 Demo
1. Component(抽象构件)
创建一个 Beverage(饮料) 抽象类
public abstract class Beverage {
// 每种饮料的描述:“名称,配料1,配料2...”
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
// 每种类型饮料的价格不同,故定义为抽象方法
public abstract double cost();
}
2. ConcreteComponent(具体构件)
有几种饮料,比如咖啡、奶茶,它们都是饮料的一种所以继承Beverage类
public class DarkRoast extends Beverage {
public DarkRoast() {
description = "DarkRoast ";
}
@Override
public double cost() {
return 3.00;
}
}
public class Decaf extends Beverage {
public Decaf() {
description = "Decaf";
}
@Override
public double cost() {
return 1.00;
}
}
3. Decorator(抽象装饰类)
public abstract class CondimentDecorator extends Beverage {
// 因为每种配料对饮料的描述进行了附加的说明,所以每种饮料添加配料后,都需要重写 getDescription方法。
@Override
public abstract String getDescription();
}
4. ConcreteDecorator(具体装饰类)
具体增加的装饰(增强)
public class Milk extends CondimentDecorator {
// 关联了一种饮料,以便对附加新的行为
Beverage beverage;
public Milk(Beverage beverage) {
this.beverage = beverage;
}
// 附加新的行为,描述
@Override
public String getDescription() {
return beverage.getDescription() + ",Milk";
}
// 附加新的行为,价格
@Override
public double cost() {
return beverage.cost() + 0.10;
}
}
public class Mocha extends CondimentDecorator {
// 关联了一种饮料,以便对附加新的行为
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
// 附加新的行为,描述
@Override
public String getDescription() {
return beverage.getDescription() + ",Mocha";
}
// 附加新的行为,价格
@Override
public double cost() {
return beverage.cost() + 1.10;
}
}
5. Test 类:
public class Test {
public static void main(String[] args) {
// 创建 Beverage 饮料,不加配料。
Beverage espresso = new Espresso();
System.out.println(espresso.getDescription() +" " + espresso.cost());
// 创建 DarkRoast 饮料,加配料 Mocha、Mocha。
Beverage darkRoast = new DarkRoast();
darkRoast = new Mocha(darkRoast);
darkRoast = new Milk(darkRoast);
System.out.println(espresso.getDescription() +" " + espresso.cost());
}
3.2 总结
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
- 为交互对象之间的松耦合设计而努力
- 对扩展开放,对修改关闭(可扩展饮料、配料,而不修改原代码,这种场景下使用装饰者模式,体现了此原则)
4. JDK源码中的装饰者设计模式
InputStream为例子,InputStream有很多的实现类:
FileInputStream:实现文件的读取。
DataInputStream:读取各种基本数据类型的数据。
BufferedInputStream:可缓存的文件流。
ObjectInputStream:读取对象的文件流。
其他的实现还有很多很多,这里的实现就使用了装饰者模式,保证InputStream不变的前提下,增加其他功能。想象一下,如果要同时实现文件读取和可缓存,那么就可以这样写:
new BufferedInputStream(new FileInputStream(""));
如果想实现基本数据类型读取+文件读取,就可以这样写:
new DataInputStream(new FileInputStream(""));
InputStream是一个抽象类,定义了read方法,代码作了精简:
public abstract class InputStream implements Closeable{
public abstract int read() throws IOException;
}
BufferedInputStream是InputStream子类的子类,在继承关系上,BufferedInputStream继承FilterInputStream,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();
}
}
而BufferedInputStream以及DataInputStream都是对FilterInputStream再做一些功能上的增强,很巧妙的实现了在不必改变原类文件情况下,允许向一个现有的对象添加新的功能。
扩展:开闭原则
开放关闭原则:类应该对扩展开放,对修改关闭。