概述
裝飾模式可以在不改變一個對象本身功能的基礎上給對象增加額外的新行爲,在現實生活 中,這種情況也到處存在,例如一張照片,我們可以不改變照片本身,給它增加一個相框, 使得它具有防潮的功能,而且用戶可以根據需要給它增加不同類型的相框,甚至可以在一個 小相框的外面再套一個大相框。
裝飾模式是一種用於替代繼承的技術,它通過一種無須定義子類的方式來給對象動態增加職 責,使用對象之間的關聯關係取代類之間的繼承關係。在裝飾模式中引入了裝飾類,在裝飾 類中既可以調用待裝飾的原有類的方法,還可以增加新的方法,以擴充原有類的功能。
定義
裝飾模式(Decorator Pattern):動態地給一個對象增加一些額外的職責,就增加對象功能來說, 裝飾模式比生成子類實現更爲靈活。裝飾模式是一種對象結構型模式。
在裝飾模式中,爲了讓系統具有更好的靈活性和可擴展性,我們通常會定義一個抽象裝飾 類,而將具體的裝飾類作爲它的子類,裝飾模式結構如圖所示:
在裝飾模式結構圖中包含如下幾個角色:
-
Component(抽象構件):它是具體構件和抽象裝飾類的共同父類,聲明瞭在具體構件中實 現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之後的對 象,實現客戶端的透明操作。
-
ConcreteComponent(具體構件):它是抽象構件類的子類,用於定義具體的構件對象,實 現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。
-
Decorator(抽象裝飾類):它也是抽象構件類的子類,用於給具體構件增加職責,但是具體 職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前 構件對象的方法,並通過其子類擴展該方法,以達到裝飾的目的。
-
ConcreteDecorator(具體裝飾類):它是抽象裝飾類的子類,負責向構件添加新的職責。每 一個具體裝飾類都定義了一些新的行爲,它可以調用在抽象裝飾類中定義的方法,並可以增 加新的方法用以擴充對象的行爲。
由於具體構件類和裝飾類都實現了相同的抽象構件接口,因此裝飾模式以對客戶透明的方式 動態地給一個對象附加上更多的責任,換言之,客戶端並不會覺得對象在裝飾前和裝飾後有 什麼不同。裝飾模式可以在不需要創造更多子類的情況下,將對象的功能加以擴展。
裝飾模式的核心在於抽象裝飾類的設計,其典型代碼如下所示:
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子類的對象再注入其中進行多次裝飾,從而對原有功能的多次擴展。
實例
現在需要給一些窗體、文本框、列表框等,添加滾動條或黑邊框效果,使用裝飾模式來實現此效果。
抽象構件類:
public abstract class Component {
public abstract void display();
}
具體構件類:
public class Window extends Component {
@Override
public void display() {
System.out.println("顯示窗體.");
}
}
public class TextBox extends Component {
@Override
public void display() {
System.out.println("顯示文本框.");
}
}
public class ListBox extends Component {
@Override
public void display() {
System.out.println("顯示列表框.");
}
}
抽象裝飾器類:
public class ComponentDecorator extends Component {
//維持對抽象構件對象的引用
private Component component;
//注入抽象構件
public ComponentDecorator(Component component) {
this.component = component;
}
@Override
public void display() {
//調用抽象構件邏輯
component.display();
}
}
滾動條裝飾器類:
public class ScrollBarDecorator extends ComponentDecorator {
public ScrollBarDecorator(Component component) {
super(component);
}
@Override
public void display() {
super.display();
setScrollBar();
}
public void setScrollBar() {
System.out.println("添加滾動條.");
}
}
黑邊框裝飾器類:
public class BlackBorderDecorator extends ComponentDecorator{
public BlackBorderDecorator(Component component) {
super(component);
}
@Override
public void display() {
//構件類原有邏輯
super.display();
//添加新的邏輯
setBlackBorder();
}
public void setBlackBorder() {
System.out.println("添加黑色邊框.");
}
}
測試代碼:
@Test
public void testDecorator() {
Component c1, c2, c3;
c1 = new Window();
c1.display();
System.out.println("-----");
//添加一個黑色邊框
c2 = new BlackBorderDecorator(c1);
c2.display();
System.out.println("-----");
//添加滾動條
c3 = new ScrollBarDecorator(c2);
c3.display();
}
結果:
使用 IDEA 將結構圖呈現出來:
總結
裝飾模式降低了系統的耦合度,可以動態增加或刪除對象的職責,並使得需要裝飾的具體構 件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。在軟件開發中, 裝飾模式應用較爲廣泛,例如在JavaIO中的輸入流和輸出流的設計、javax.swing包中一些圖形 界面構件功能的增強等地方都運用了裝飾模式。
主要優點
- 對於擴展一個對象的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
- 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的 具體裝飾類,從而實現不同的行爲。
- 可以對一個對象進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合, 可以創造出很多不同行爲的組合,得到功能更爲強大的對象。
- 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝 飾類,原有類庫代碼無須改變,符合“開閉原則”
主要缺點
- 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在於它們之間相互連接 的方式有所不同,而不是它們的類或者屬性值有所不同,大量小對象的產生勢必會佔用更多 的系統資源,在一定程序上影響程序的性能。
- 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味着比繼承更加易於出 錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較爲繁瑣。
適用場景
- 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。
- 當不能採用繼承的方式對系統進行擴展或者採用繼承不利於系統擴展和維護時可以使用裝 飾模式。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴展,爲支持每 一種擴展或者擴展之間的組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因 爲類已定義爲不能被繼承(如Java語言中的final類)。