人生苦短,不如养狗
一、从逛4S店说起
上周心血来潮去了一趟小鹏的体验店,一进门,销售的小哥就开始给我介绍各种不同配置的车,什么智享版、智尊版,听得我是头晕脑胀,赶紧告辞。
走出店门,凉风拂面,顿觉大脑清醒了不少,不禁又开始琢磨起刚刚销售小哥跟我说的车型配置信息。琢磨了一会,突然灵光一闪,这玩意儿竟然可以用 装饰器模式 实现。
二、基本概念
1. 什么是装饰器模式
装饰器模式是一种 对象结构型模式 ,它通过一种无须定义子类的方式来给对象动态增加职责/功能,使用对象之间的关联关系取代类之间的继承关系。其结构如下图所示:
在上面的类图中可以看到以下四个角色:
Component(抽象构件)
:需要被装饰类的基类,同时也是装饰者的基类,在这个基类中声明了需要实现的业务方法;ConcreteCompnent(具体构件)
:作为需要被装饰的类,在具体构件中只需要实现最基础的业务逻辑即可;Decorator(抽象装饰器)
:抽象装饰器维护了一个指向抽象构件对象的引用(子类通过构造器等方法明确使用何种具体构件),即通过组合方式将装饰者和被装饰者建立起一个相比继承关系更为宽松的联系。同时作为抽象构件的子类,抽象装饰器还给具体构件增加了额外的职责,其额外的职责在抽象装饰器的子类中得到实现;ConcreteDecorator(具体装饰器)
:作为抽象装饰器的子类,具体装饰器实现了需要给具体构件添加的额外职责;
2. 一个小例子
为了更好地区分继承关系和装饰者模式的不同,下面我们分别用 继承的方式 和 装饰者模式 来实现一下小鹏P7和P5当中的不同配置。
2.1 基于继承关系的小鹏汽车系列
首先我们通过继承的方式来实现以下小鹏P7和P5的原始车型、附加了自动驾驶、附加了鹏翼门、附加了定制音响系统不同配置的车型。
这里就不展示对应的代码,直接给出一张类图来阐述对应的实现。从上面的类图中我们可以看到,在不考虑配置交叉组合的情况下已经实现了8个子类,如果出现功能需要交叉组合的情况,就会出现子类保障的情况。那么在装饰器模式中,这一弊病是否会得到缓解?
2.2 基于装饰器模式的小鹏汽车系列
有了上面的概念,我们可以尝试使用装饰者模式来实现一下小鹏汽车各种类型、各种配置的汽车。
上面类图中具体各个类的实现如下:
PengCar
package DecoratorPattern;
/**
* 小鹏汽车
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:50 下午
*/
public abstract class PengCar {
/**
* 运行
*/
abstract void run();
}
PengFiveCar
package DecoratorPattern;
/**
* 小鹏P5
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:51 下午
*/
public class PengFiveCar extends PengCar{
@Override
void run() {
System.out.println("小鹏P5");
}
}
PengSevenCar
package DecoratorPattern;
/**
* 小鹏P7
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:50 下午
*/
public class PengSevenCar extends PengCar{
@Override
void run() {
System.out.println("小鹏P7");
}
}
PengDecorator
package DecoratorPattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:52 下午
*/
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class PengDecorator extends PengCar{
private PengCar pengCar;
public PengDecorator(PengCar pengCar) {
this.pengCar = pengCar;
}
@Override
void run() {
// 调用汽车本身的能力
pengCar.run();
}
}
AutoDriveDecorator
package DecoratorPattern;
/**
* 自动驾驶装饰器
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:54 下午
*/
public class AutoDriveDecorator extends PengDecorator {
public AutoDriveDecorator(PengCar pengCar) {
super(pengCar);
}
@Override
void run() {
super.run();
System.out.println("增加自动驾驶!");
}
}
PengDoorDecorator
package DecoratorPattern;
/**
* 鹏翼门装饰器
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:56 下午
*/
public class PengDoorDecorator extends PengDecorator{
public PengDoorDecorator(PengCar pengCar) {
super(pengCar);
}
@Override
void run() {
super.run();
System.out.println("增加鹏翼门!");
}
}
PengSpeakerDecorator
package DecoratorPattern;
/**
* 定制音响装饰器
*
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:57 下午
*/
public class PengSpeakerDecorator extends PengDecorator {
public PengSpeakerDecorator(PengCar pengCar) {
super(pengCar);
}
@Override
void run() {
super.run();
System.out.println("增加定制音响!");
}
}
App
package DecoratorPattern;
/**
* @author brucebat
* @version 1.0
* @since Created at 2021/5/23 10:59 下午
*/
public class App {
public static void main(String[] args) {
// 以小鹏P5为例
PengFiveCar pengFiveCar = new PengFiveCar();
AutoDriveDecorator autoDriveDecorator = new AutoDriveDecorator(pengFiveCar);
PengDoorDecorator pengDoorDecorator = new PengDoorDecorator(autoDriveDecorator);
PengSpeakerDecorator pengSpeakerDecorator = new PengSpeakerDecorator(pengDoorDecorator);
pengSpeakerDecorator.run();
}
}
最终结果
小鹏P5
增加自动驾驶!
增加鹏翼门!
增加定制音响!
从最终的示例当中可以看到,小鹏P5本身的职责和装饰器的职责相对独立,如果想要进行不同职责的排列组合,只需要使用对应的装饰器对被装饰者进行职责添加即可,无须进进行额外的子类实现。
3. 浅析优劣
3.1 装饰器模式的有点
作为设计模式中的一种,装饰器模式可谓是将开闭原则诠释到了极致,极其灵活的实现了对象功能的扩展,而不会造成继承带来的子类个数爆炸的情况。而且在上面的例子中可以看到,在进行功能扩展的过程中可以对一个对象进行多次装饰,更加灵活地实现了多种功能的排列组合,从而创造出具有更多能力的对象。
当然最重要的一点,也就是上面能力实现的基础,就是构件和装饰器之间通过组合方式而不是继承关系关联在一起,两者可以相对独立的进行变化运行。
3.2 装饰模式的缺点
虽然在使用上装饰器提供了一种比继承者模式更为灵活的对象功能的扩展能力,但是这也带来了另一个问题,就是在多次装饰之后,进行调试或者问题排查时需要逐级倒推进行排查,整体的排查流程会变得较为繁琐。
三、Java IO中的装饰器模式
如此优秀的设计模式,JDK中也有对应使用,比如其中的IO类。
这里只挑选了 FileInputStream
和 BufferedInputStream
这两个类来简单欣赏下jdk中是如何使用装饰器模式的,从上面的类图可以看到, FileInputStream
就是我们上面说的具体构件,而 BufferedInputStream
则是具体装饰器。在IO中具体构件一般用于指出数据来源格式,比如上面的 FileInputStream
说明数据是从 File
文件中获取,而具体装饰器则在原本IO操作的基础上加入了额外的功能,比如在 BufferedInpuStream
通过使用缓冲流的方式对输入数据进行处理,增加了缓冲的功能。
除了上面提到的两个类,Java IO类库中还有其他相应的类,有兴趣的同学可以阅读源码深入了解一下。