在現實生活中我們的汽車都具備跑的功能,我們可以不改變汽車原有功能的前提下,把它放入一個裝修廠,開進去讓里面給咱們的車子做一些裝飾,開出來之后呢,就具備了上天的功能了(技術可達是可以的哈),這就給原來的汽車對象,增加了額外的功能。
再舉一個例子:假設我們非常愛惜一張照片,我們可以不改變照片本身前提下,給它增加一個相框,使得它具有防潮的功能,而且用戶可以根據需要給它增加不同類型的相框,甚至可以在一個小相框的外面再套一個大相框。這就是針對照片這個對象的裝飾,在軟件工程中,同樣存在類似的功能,使用裝飾模式可以透明的增加指定對象的功能。
1、裝飾模式的引入
首先咱們來看一段代碼:
假設我要設計一個汽車類,然后在里面定義了汽車可能存在的功能(后面我還要擴展汽車的功能):
public class Car {
public void run(){
System.out.println("能跑");
}
public void fly(){
System.out.println("能飛");
}
public void sweep(){
System.out.println("能游");
}
public void show(){
System.out.println("該汽車擁有的功能:");
this.run();
this.fly();
this.sweep();
}
}
public class Main {
public static void main(String[] args) {
Car bus = new Car();
bus.show();
}
}
這段代碼并沒有什么設計可言的。運行結果就不貼出來了,可見,我們在客戶端造了一個巴士,調用了巴士的 show 方法后,發現有一些功能并不是巴士的(飛、游泳),這樣顯然是存在問題的。那么我們可能會作如下修改:
使用繼承,每個繼承體系歸類。
//接口可以改為抽象類
public interface Car {
//只要是有汽車,都具備跑的功能
void run();
//調用展示該汽車存在的功能
void show();
}
public class RunCar implements Car{
//普通汽車只具備跑的功能
public void run() {
System.out.println("可以跑");
}
public void show() {
this.run();
}
}
public class FlyCar implements Car {//擴展的汽車具備飛的功能
@Override
public void run() {
System.out.println("可以跑");
}
// 定義自己的功能
public void fly() {
System.out.println("可以飛");
}
@Override
public void show() {
this.run();
this.fly();
}
}
public class SwimCar implements Car{//擴展的汽車具備游泳的功能
@Override
public void run() {
System.out.println("可以跑");
}
public void swim(){
System.out.println("可以游泳");
}
@Override
public void show() {
this.run();
this.swim();
}
}
然后在客戶端調用:
public class Main {
public static void main(String[] args) {
Car bus = new RunCar();
bus.show();
Car flyCar = new FlyCar();
flyCar.show();
}
}
運行結果如下:
我們這里使用繼承的方式來擴展系統(Car)的功能,這樣擁有了設計可言,但是還是存在問題的。問題在于如果增加子類,他擁有“遁地”的功能的話(當然技術先進可以做到哈,不要在意這些細節),仍然要在遁地這個子類里面定義額外的“遁地”方法。這個時候,為了解決這種繼承擴展功能問題,就引入了本節的內容——裝飾模式。裝飾模式是擴展系統功能的最佳選擇。裝飾模式是對已有對象的功能進行擴展(裝修、裝飾),以獲得更加符合用戶需求的對象,使得對象具有更加強大的功能。
2、裝飾模式概述
裝飾模式可以在不改變一個對象本身功能的基礎上給對象增加額外的新行為,在現實生活中,這種情況也到處存在,例如裝修窗戶,我們可以不改變窗戶本身,給它增加一些額外的裝飾(比如窗花),增加他的可觀賞性,而且用戶可以根據需要給它增加不同類型的窗花,甚至可以裝飾多層。
裝飾模式是一種用于替代繼承的技術,它通過一種無須定義子類的方式來給對象動態增加職責,使用對象之間的關聯關系取代類之間的繼承關系。在裝飾模式中引入了裝飾類,在裝飾類中既可以調用待裝飾的原有類的方法,還可以增加新的方法,以擴充原有類的功能。
裝飾模式定義如下:
動態地給一個對象增加一些額外的職責,就增加對象功能來說,裝飾模式比生成子類實現更為靈活。裝飾模式是一種對象結構型模式。
在裝飾模式中,為了讓系統具有更好的靈活性和可擴展性,我們通常會定義一個抽象裝飾類,而將具體的裝飾類作為它的子類,裝飾模式結構如圖所示:
在裝飾模式結構圖中包含如下幾個角色:
Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明了在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象,實現客戶端的透明操作。【上述 Car 就是這個角色】
ConcreteComponent(具體構件):它是抽象構件類的子類,用于定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。【上述 RunCar 就是這個角色】
Decorator(抽象裝飾類):它也是抽象構件類的子類,用于給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,并通過其子類擴展該方法,以達到裝飾的目的。比如針對開頭的案例,它可以對 Car 增加除了跑額外的功能 “可以游泳”、“下水”、“遁地”等。
ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每一個具體裝飾類都定義了一些新的行為,它可以調用在抽象裝飾類中定義的方法,并可以增加新的方法用以擴充對象的行為。【上述 FlyCar 和 SwinCar 可以作為整個角色對 Car 動能進行擴展】
由于具體構件類和裝飾類都實現了相同的抽象構件接口,因此裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端并不會覺得對象在裝飾前和裝飾后有什么不同。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。
裝飾模式的核心在于抽象裝飾類的設計,其典型代碼如下所示:
class Decorator implements Component{
//持有抽象構件的引用
private Component component;
//注入一個抽象構件類型的對象(依賴倒置)
public Decorator(Component component) {
this.component=component;
}
public void operation(){
//調用原有業務方法
component.operation();
}
}
在抽象裝飾類 Decorator 中定義了一個 Component 類型的對象 component,維持一個對抽象構件對象的引用,并可以通過構造方法或 Setter 方法將一個 Component 類型的對象注入進來,同時由于 Decorator 類實現了抽象構件 Component 接口,因此需要實現在其中聲明的業務方法 operation(),需要注意的是在 Decorator 中并未真正實現 operation() 方法,而只是調用原有 component 對象的 operation() 方法,它沒有真正實施裝飾,而是提供一個統一的接口,將具體裝飾過程交給子類完成。
在 Decorator 的子類即具體裝飾類中將繼承 operation() 方法并根據需要進行擴展,典型的具體裝飾類代碼如下:
class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component){
super(component);
}
public void operation(){
super.operation(); //調用原有業務方法
addedBehavior(); //調用新增業務方法
}
//新增業務方法
public void addedBehavior(){
……
}
}
在具體裝飾類中可以調用到抽象裝飾類的 operation() 方法,同時可以定義新的業務方法,如 addedBehavior()。
由于在抽象裝飾類 Decorator 中注入的是 Component 類型的對象,因此我們可以將一個具體構件對象注入其中,再通過具體裝飾類來進行裝飾;此外,我們還可以將一個已經裝飾過的 Decorator 子類的對象再注入其中進行多次裝飾,從而對原有功能的多次擴展。
3、裝飾模式實戰
針對文章開頭處的案例,使用裝飾設計模式進行修改。
對于裝飾模式,可以難于理解的地方在于 Decorator 抽象裝飾類為何會繼承或者實現 Component 抽象構建類。如果我們不繼承 Component 構建類使用裝飾模式的時候,代碼如下:
UML圖:
代碼:
```java
//Component抽象構件角色
public interface Car {
//只要是有汽車,都具備跑的功能