學習、探究Java設計模式——裝飾者模式__Java

來源:互聯網
上載者:User
定義

裝飾者模式:在不改變原類檔案以及不使用繼承的情況下,動態地將責任附加到對象上,從而實現動態拓展一個對象的功能。它是通過建立一個封裝對象,也就是裝飾來包裹真實的對象。 設計原則

要使用裝飾者模式,需要滿足以下設計原則:
1、多用組合,少用繼承
2、開放-關閉原則:類應該對拓展開放,對修改關閉 UML類圖

我們先來看看裝飾者模式的類圖,再來詳細講述:

由上自下:
1、Component是基類。通常是一個抽象類別或者一個介面,定義了屬性或者方法,方法的實現可以由子類實現或者自己實現。通常不會直接使用該類,而是通過繼承該類來實現特定的功能,它約束了整個繼承樹的行為。比如說,如果Component代表人,即使通過裝飾也不會使人變成別的動物。
2、ConcreteComponent是Component的子類,實現了相應的方法,它充當了“被裝飾者”的角色。
3、Decorator也是Component的子類,它是裝飾者共同實現的抽象類別(也可以是介面)。比如說,Decorator代表衣服這一類裝飾者,那麼它的子類應該是T恤、裙子這樣的具體的裝飾者。
4、ConcreteDecorator是Decorator的子類,是具體的裝飾者,由於它同時也是Component的子類,因此它能方便地拓展Component的狀態(比如添加新的方法)。每個裝飾者都應該有一個執行個體變數用以儲存某個Component的引用,這也是利用了組合的特性。在持有Component的引用後,由於其自身也是Component的子類,那麼,相當於ConcreteDecorator包裹了Component,不但有Component的特性,同時自身也可以有別的特性,也就是所謂的裝飾A Sample

為了更加深刻地理解裝飾者模式,我們來看一個簡單的栗子。首先,我們假設現在有這樣一個需求:你有一家服裝店,賣各式各樣的衣服,現在需要用一個系統來記錄客戶所要購買的衣服的總價,以便方便地結算。那麼在這個例子裡面,我們可以用裝飾者模式,把客戶當做被裝飾者,衣服是裝飾者,這很直觀形象吧,接著我們來一步步實現需求。 Step 1、建立Component基類

因為總體對象是人,所以我們可以把人抽象為基類,建立Person.java:

public abstract class Person {    String description = "Unkonwn";    public String getDescription()    {        return description;    }    public abstract double cost(); //子類應該實現的方法}
Step 2、建立被裝飾者——ConcreteComponent

客戶分為很多種,有兒童、青少年、成年人等,因此我們可以建立不同的被裝飾者,這裡我們建立青少年的被裝飾者,建立Teenager.java

public class Teenager extends Person {    public Teenager() {        description = "Shopping List:";    }    @Override    public double cost() {        //什麼都沒買,不用錢        return 0;    }}
Step 3、建立Decorator

由於不同的部位有不同的衣物,不能混為一談,比如說,衣服、帽子、鞋子等,那麼這裡我們建立的Decorator為衣服和帽子,分別建立ClothingDecorator.javaHatDecorator.java:

public abstract class ClothingDecorator extends Person {    public abstract String getDescription();}
public abstract class HatDecorator extends Person {    public abstract String getDescription();}
Step 4、建立ConcreteDecorator

上面既然已經建立了兩種Decorator,那麼我們基於它們進行拓展,建立出不同的裝飾者,對於Clothing,我們建立Shirt.java,對於Hat,我們建立Casquette,其實可以根據不同類型的衣物建立更多不同的裝飾者,這裡只是作為示範而建立了兩種。代碼如下所示:

public class Shirt extends ClothingDecorator {    //用執行個體變數儲存Person的引用    Person person;    public Shirt(Person person)    {        this.person = person;    }    @Override    public String getDescription() {        return person.getDescription() + "a shirt  ";    }    @Override    public double cost() {        return 100 + person.cost(); //實現了cost()方法,並調用了person的cost()方法,目的是獲得所有累加值    }}
public class Casquette extends HatDecorator {    Person person;    public Casquette(Person person) {        this.person = person;    }    @Override    public String getDescription() {        return person.getDescription() + "a casquette  "; //鴨舌帽    }    @Override    public double cost() {        return 75 + person.cost();    }}

最後我們在測試類別內測試我們的代碼:

public class Shopping {    public static void main(String[] args) {        Person person = new Teenager();        person = new Shirt(person);        person = new Casquette(person);        System.out.println(person.getDescription() + " ¥ " +person.cost());    }}

先建立一個Teenager對象,接著用Shirt裝飾它,就變成了穿著Shirt的Teenager,再用Casquette裝飾,就變成了戴著Casquette的穿著Shirt的Teenager。運行結果如下所示:

我們梳理一下以上的邏輯,畫出如下所示的韋恩圖:

Teenager、Shirt、Casquette都是繼承自Person基類,但是具體實現不同,Teenager是Person的直接子類,表示了被裝飾者;Teenager、Shirt是裝飾者,儲存了Person的引用,實現了cost()方法,並且在cost()方法內部,不但實現了自己的邏輯,同時也調用了Person引用的cost()方法,即擷取了被裝飾者的資訊,這是裝飾者的一個特點,儲存引用的目的就是為了擷取被裝飾者的狀態資訊,以便將自身的特性加以組合。 特點

以上就是裝飾者模式的一個小栗子,講述了裝飾者的基本用法。通過上述的例子,我們可以總結一下裝飾者模式的特點。
(1)裝飾者和被裝飾者有相同的介面(或有相同的父類)。
(2)裝飾者儲存了一個被裝飾者的引用。
(3)裝飾者接受所有用戶端的請求,並且這些請求最終都會返回給被裝飾者(參見韋恩圖)。
(4)在運行時動態地為對象添加屬性,不必改變對象的結構。

使用裝飾者模式的最大好處就是其拓展性十分良好,通過使用不同的裝飾類來使得對象具有多種多樣的屬性,靈活性比直接繼承好。然而它也有缺點,那就是會出現很多小類,即裝飾類,使程式變得複雜。 應用

學習了裝飾者模式用法、特點以及優缺點後,我們再來看看裝飾者模式在實際開發過程的應用。裝飾者模式在Java中經常出現的地方就是JavaIO。提到JavaIO,腦海中就冒出了大量的類:InputStream、FileInputStream、BufferedInputStream……等,真是頭都大了,其實,這裡面大部分都是裝飾類,只要弄清楚這一點就容易理解了。我們來看看JavaIO是怎樣使用裝飾者模式的。
從字元流來分析,我們知道,有兩個基類,分別是InputStream和OutputStream,它們也就是我們上面所述的Component基類。接著,它有如下子類:FileInputStream、StringBufferInputStream等,它們就代表了上面所述的ConcreteComponent,即裝飾對象。此外,InputStream還有FilterInputStream這個子類,它就是一個抽象裝飾者,即Decorator,那麼它的子類:BufferedInputStream、DataInputStream等就是具體的裝飾者了。那麼,從裝飾者模式的角度來看JavaIO,是不是更加容易理解了呢。

下面,我們來自己實現自己的JavaIO的裝飾者。要實現的功能是:把一段話裡面的每個單詞的首字母大寫。我們先建立一個類:UpperFirstWordInputStream.java

public class UpperFirstWordInputStream extends FilterInputStream {    private int cBefore = 32;    protected UpperFirstWordInputStream(InputStream in) {        //由於FilterInputStream已經儲存了裝飾對象的引用,這裡直接調用super即可        super(in);    }    public int read() throws IOException{        //根據前一個字元是否是空格來判斷是否要大寫        int c = super.read();        if(cBefore == 32)        {            cBefore = c;            return (c == -1 ? c: Character.toUpperCase((char) c));        }else{            cBefore = c;            return c;        }    }}

接著編寫一個測試類別:InputTest.java

public class InputTest {    public static void main(String[] args) throws IOException {        int c;        StringBuffer sb = new StringBuffer();        try {            //這裡用了兩個裝飾者,分別是BufferedInputStream和我們的UpperFirstWordInputStream            InputStream in = new UpperFirstWordInputStream(new BufferedInputStream(new FileInputStream("test.txt")));            while((c = in.read()) >= 0)            {                sb.append((char) c);            }            System.out.println(sb);        } catch (FileNotFoundException e) {            e.printStackTrace();        }    }}

(注意:上面的test.txt檔案需要你自行建立,放到同一個檔案夾內即可,內容可隨意填寫。)
最後,我們看下運行結果:

好了,本篇文章已經詳細地介紹了裝飾者模式,希望讀者能夠理解並運用到自己的項目中,謝謝你們的閱讀~

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.