1、装饰器模式
通常情况下,我们使用继承或组合来扩展一个类的行为,但是这些都是在编译时完成的,它适用于类的所有实例,并且随着扩展功能的增多,子类会很膨胀,我们不能在运行时添加或删除任何现有行为来达到复用的目的。为了解决这个问题,于是就有了装饰器模式。
比如我们熟悉的java IO类中就有大量装饰器模式的使用。例如FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等。
1.1、什么是装饰器模式
-
定义
装饰器(Decorator)模式一种结构型设计模式,是一种用于代替继承的技术,指在不改变现有对象结构的情况下,动态地给当前对象添加一些额外的功能。
装饰器模式的意图在于:运行时修改对象的功能,比生成子类更加灵活。
装饰器模式的结构:
1)抽象构件(Component)角色:定义一个抽象接口以规范子类的对象的行为,所有的包装类(装饰对象)和被包装类(真实对象)都继承自这个接口。比如:io流中InputStream、OutPutStream、Reader、Writer;
2)具体构件(ConcreteComponent)角色:指被包装类的实现类(真实对象)。比如:io流中FileInputStream、FileOutputStream、ObjectInputStream等;
3)装饰(Decorator)角色:所有包装类,都继承自Decorator(抽象)类,可以通过其子类扩展具体构件的功能。比如:io流中的FilterInputStream、FilterOutputStream;
4)具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,用于扩充被包装类的功能。比如:io流中的BufferedInputStream、DataInputStream、BufferedOutputStream;
对于java io流中的输入流而言,它的体系结构如上图所示。
Component角色:InputStream声明了输入流的实现规范;
ConcreteComponent角色:FileInputStream,ObjectInputStream、ByteArrayInputStream、PipedInputStream是输入流中为不同场景提供的实现;
Decorator角色:FilterInputStream抽象装饰,是为了增强ConcreteComponent角色的抽象类;
ConcreteDecorator角色:BufferedInputStream、DataInputStream是装饰器的实现类,是具体的增强方式。
以前我们用io流去读一个文件的时候,是不是经常会加一个缓冲流,提高数据读取的效率。
InputStream in = new FileInputStream("D:\\bigTest.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[1024];
while (bin.read(data) != -1) {
//...
}
那为什么不直接给FileInputStream接入缓存功能或者增加一个具有缓存功能的FileInputStream的子类呢?由上图我们可以看到,如果InputStream只有一个实现类的话,这样做完全没有问题,可是InputStream的实现类有很多个,如果给每个子类都加加上增强功能的子类,可想而知,此时的子类数量是非常庞大的。而我们的装饰器模式就是用来代替继承并且增强对象的。
1.2、装饰器模式的优缺点
-
优点
1)装饰模式是继承的一个替代模式,可以动态扩展一个实现类的功能;
2)可以通过将一个对象包装到多个装饰器中来组合多个行为;
3)也可以将大的可变整体划分成较小的类,比较符合单一职责原则;
4)装饰器模式完全遵守开闭原则。
-
缺点
装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
1.3、创建方式
1)按照装饰器模式的结构依次创建
/**
* Component-抽象构件
*/
public interface ICar {
void assemble(); //定义汽车组装行为
}
/**
* ConcreteComponent-具体构件
*/
class BasicCar implements ICar{
@Override
public void assemble() {
System.out.println("最低配的普通车");
}
}
/**
* Decorator-抽象装饰角色
*/
abstract class AbstractCar implements ICar{
private ICar car;
public AbstractCar(ICar car){
this.car = car;
}
@Override
public void assemble() {
car.assemble();
}
//作为抽象方法,可以声明一些希望子类实现的功能
}
//ConcreteDecorator-具体的装饰器(普通越野款)
class SportsCar extends AbstractCar{
public SportsCar(ICar car) {
super(car);
}
@Override
public void assemble() {
super.assemble();
System.out.println("装配高底盘,我是越野款");
}
}
//ConcreteDecorator-具体的装饰器(高端款)
class LuxuryCar extends AbstractCar{
public LuxuryCar(ICar car) {
super(car);
}
@Override
public void assemble() {
super.assemble();
System.out.println("装配高端发动机,我是高档款");
}
}
2)创建客户端
public class Client {
public static void main(String[] args) {
//最基础款的车
BasicCar car = new BasicCar();
car.assemble();
System.out.println("顾客一:第一次改装:-----------");
SportsCar sp1 = new SportsCar(car); //已有对象增加/修改一些功能
sp1.assemble();
System.out.println("顾客二:第一次改装:-----------");
LuxuryCar lc2 = new LuxuryCar(car); //已有对象增加/修改一些功能
lc2.assemble();
System.out.println("顾客一:第二次改装:-----------");
LuxuryCar lc1 = new LuxuryCar(sp1); //传SportsCar,就是在此版本上修改,这样组装非常灵活
lc1.assemble();
}
}
-
案例效果
在java io流中,我们也可以对原有对象实现自己的一些特殊处理。比如,下面的案例自定义实现转大写装饰器
public class toUpperCaseInputStream extends FilterInputStream{
protected toUpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase(c));
}
public static void main(String[] args) throws IOException {
int c;
//使用自定义的装饰器去装饰ByteArrayInputStream对象
InputStream in = new toUpperCaseInputStream(new ByteArrayInputStream(new String("abcd").getBytes()));
try {
while ((c = in.read()) >= 0) {
System.out.print((char) c); //ABCD
}
} finally {
in.close();
}
}
}
1.4、总结及建议
装饰器提供运行时修改能力,因此更加灵活。当可供选择的数量越多时,使用装饰器模式会更加灵活。
应用场景:
1)当您需要能够在运行时为对象分配额外的行为而不破坏使用这些对象的代码时,请使用装饰器模式。
2)当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时(生成子类会产生大量子类),请使用装饰器模式。
3)对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现时。
JDK中装饰器模式的应用:
java io流中的FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等都是抽象装饰类。