
人生苦短,不如養狗
一、從逛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類庫中還有其他相應的類,有興趣的同學可以閱讀源碼深入了解一下。