1. 程式人生 > 實用技巧 >裝飾者模式

裝飾者模式

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是作為現有的類的一個包裝。

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。

優點:

  • 裝飾者模式比繼承靈活性,在不改變原有物件的情況下給物件擴充套件功能,符合開閉原則。繼承關係是靜態的,在編譯的時候就已經決定了行為,不便於控制增加行為的方式和時機。

  • 裝飾者模式可以動態使用不同的裝飾類排列組合,創造出多樣的行為組合。

缺點:

  • 1.裝飾模式會導致設計出大量的ConcreteDecorator類,增加系統的複雜性。

  • 2.對於多次裝飾的物件,一旦出現錯誤,排錯繁瑣;

裝飾模式的結構與實現

通常情況下,擴充套件一個類的功能會使用繼承方式來實現。但繼承具有靜態特徵,耦合度高,並且隨著擴充套件功能的增多,子類會很膨脹。如果使用組合關係來建立一個包裝物件(即裝飾物件)來包裹真實物件,並在保持真實物件的類結構不變的前提下,為其提供額外的功能,這就是裝飾模式的目標。下面來分析其基本結構和實現方法。

1. 模式的結構

裝飾模式主要包含以下角色。

  1. 抽象構件(Component)角色:定義一個抽象介面以規範準備接收附加責任的物件。

  2. 具體構件(Concrete Component)角色:實現抽象構件,通過裝飾角色為其新增一些職責。

  3. 抽象裝飾(Decorator)角色:繼承抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能。

  4. 具體裝飾(ConcreteDecorator)角色:實現抽象裝飾的相關方法,並給具體構件物件新增附加的責任。

裝飾模式的結構圖如圖所示。

2. 模式的實現

裝飾模式的實現程式碼如下:

package decorator;
public class DecoratorPattern
{
    public static void main(String[] args)
    {
        Component p=new ConcreteComponent();
        p.operation();
        System.out.println(
"---------------------------------"); Component d=new ConcreteDecorator(p); d.operation(); } }
//抽象構件角色 interface Component { public void operation(); }
//具體構件角色 class ConcreteComponent implements Component { public ConcreteComponent() { System.out.println("建立具體構件角色"); } public void operation() { System.out.println("呼叫具體構件角色的方法operation()"); } }
//抽象裝飾角色 class Decorator implements Component { private Component component; public Decorator(Component component) { this.component=component; } public void operation() { component.operation(); } }
//具體裝飾角色 class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component); } public void operation() { super.operation(); addedFunction(); } public void addedFunction() { System.out.println("為具體構件角色增加額外的功能addedFunction()"); } }

應用實現

裝飾者模式可以給已經存在的物件動態的新增能力。下面,我將會用一個簡單的例子來演示一下如何在程式當中使用裝飾者模式。

1. 裝飾者模式

讓我們來假設一下,你正在尋找一個女朋友。有很多來自不同國家的女孩,比如:美國,中國,日本,法國等等,他們每個人都有不一樣的個性和興趣愛好,如果需要在程式當中模擬這麼一種情況的話,假設每一個女孩就是一個Java類的話,那麼就會有成千上萬的類,這樣子就會造成類的膨脹,而且這樣的設計的可擴充套件性會比較差。

因為如果我們需要一個新的女孩,就需要建立一個新的 Java 類,這實際上也違背了在程式開發當中需要遵循的OCP(對擴充套件開放,對修改關閉)原則。讓我們來重新做另外一種設計,讓每一種個性或者興趣愛好成為一種裝飾從而可以動態地新增到每一個女孩的身上。

2. 類圖結構

3.裝飾者模式示例程式碼

// Girl.java
public abstract class Girl {

    String description = "no particular";

    public String getDescription(){
        return description;
    }
}

// AmericanGirl.java
public class AmericanGirl extends Girl {
 
    public AmericanGirl() {
        description = "+American";
    }
}

// EuropeanGirl.java
public class EuropeanGirl extends Girl {
 
    public EuropeanGirl(){
        description = "+European";
    }
}

// GirlDecorator.java
public abstract class GirlDecorator extends Girl {
 
    public abstract String getDescription();

}

// Science.java
public class Science extends GirlDecorator {
 
    private Girl girl;
 
    public Science(Girl girl){
        this.girl = girl;
    }

    @Override
    public String getDescription() {
        return this.girl.getDescription() + "+Like Science";
    }
 
    public void caltulateStuff() {
        System.out.println("scientific calculation!");
    }
}

// Art.java
public class Art extends GirlDecorator {
    private Girl girl;
 
    public Art(Girl girl){
        this.girl = girl;
    }
 
    @Override
    public String getDescription() {
        return this.girl.getDescription() + "+Like Art";
    }

    public void draw() {
        System.out.println("draw pictures!");
    }
}

// Main.java
public class Main {
    public static void main(String[] args) {
        // 普通美國女孩
        Girl g1 = new AmericanGirl();
        System.out.println(g1.getDescription());
        // 喜歡科學的
        Science g2 = new Science(g1);
        System.out.println(g2.getDescription());
        // 喜歡藝術的
        Art g3 = new Art(g2);
        System.out.println(g3.getDescription());
    }
}

應用場景

前面講解了關於裝飾模式的結構與特點,下面介紹其適用的應用場景,裝飾模式通常在以下幾種情況使用。

  • 當需要給一個現有類新增附加職責,而又不能採用生成子類的方法進行擴充時。例如,該類被隱藏或者該類是終極類或者採用繼承方式會產生大量的子類。

  • 當需要通過對現有的一組基本功能進行排列組合而產生非常多的功能時,採用繼承關係很難實現,而採用裝飾模式卻很好實現。

  • 當物件的功能要求可以動態地新增,也可以再動態地撤銷時。

裝飾模式在Java語言中的最著名的應用莫過於 Java I/O 標準庫的設計了。例如,InputStream 的子類 FilterInputStream,OutputStream 的子類 FilterOutputStream,Reader 的子類 BufferedReader 以及 FilterReader,還有 Writer 的子類 BufferedWriter、FilterWriter 以及 PrintWriter 等,它們都是抽象裝飾類。

裝飾者模式在jdk中的應用I/O

  • InputStream 相當於裝飾者模式的 Component

  • FileInputStream,ByteArrayInputStream,ObjectInputStream這些物件直接繼承了 InputStream,相當於裝飾者模式中的 ConcreteComponent

  • FilterInputStream 繼承了 InputStream,並且持有了一個 InputStream,相當於裝飾者模式中的 Decorator

  • BufferedInputStream,PushbackInputStream,LineNumberInputStream,DataInputStream 繼承了FilterInputStream,相當於裝飾者模式中的ConcreteDecorator

//這裡FileInputStream 相當於元件物件,BufferedInputStream這個裝飾器裝飾了 FileInputStream 物件 
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("fileName"))); 
byte[] buff = new byte[1024];
bis.read(buff); System.out.println(
new String(buff));

參考文章

這是我見過最通俗易懂的 裝飾者模式 講解了!

設計模式之裝飾者模式(Decorator Pattern)

裝飾模式(裝飾設計模式)詳解