Java 設計模式之裝飾者模式

1、瞭解裝飾者模式

1.1 什麼是裝飾者模式java

裝飾者模式指的是在沒必要改變原類文件和使用繼承的狀況下,動態地擴展一個對象的功能。它是經過建立一個包裝對象,也就是裝飾者來包裹真實的對象。編程

因此裝飾者能夠動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的方案。設計模式

1.2 裝飾者模式組成結構架構

  • 抽象構件 (Component):給出抽象接口或抽象類,以規範準備接收附加功能的對象。
  • 具體構件 (ConcreteComponent):定義將要接收附加功能的類。
  • 抽象裝飾 (Decorator):裝飾者共同要實現的接口,也能夠是抽象類。
  • 具體裝飾 (ConcreteDecorator):持有一個 Component 對象,負責給構件對象「貼上」附加的功能。

1.3 裝飾者模式 UML 圖解ide

這裏寫圖片描述

1.4 裝飾者模式應用場景函數

  • 須要擴展一個類的功能,或給一個類添加附加職責。
  • 須要動態的給一個對象添加功能,這些功能能夠再動態的撤銷。
  • 須要增長由一些基本功能的排列組合而產生的很是大量的功能,從而使繼承關係變的不現實。
  • 當不能採用生成子類的方法進行擴充時。可能有大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增加。

1.5 裝飾者模式特色測試

  • 裝飾者對象和具體構件有相同的接口。這樣客戶端對象就能以和真實對象相同的方式和裝飾對象交互。
  • 裝飾者對象包含一個具體構件的引用(reference)。
  • 裝飾者對象接受全部來自客戶端的請求。它把這些請求轉發給具體構件。
  • 裝飾者對象能夠在轉發這些請求之前或之後動態增長一些功能。

2、裝飾者模式具體應用

2.1 問題描述this

星巴茲咖啡訂單系統:星巴茲店提供了各式各樣的咖啡,以及各類咖啡調料。爲了適應飲料需求供應,因此讓你設計一個更新訂單系統。spa

2.2 使用繼承設計

這裏寫圖片描述
在購買咖啡時,能夠要求在咖啡中添加各類調料,例如:豆漿 (Soy)、摩卡 (Mocha)、奶泡等。因爲各類調料的價格不相同,因此訂單系統必需要考慮這些因素。因而就有了下面的嘗試

2.3 繼承類圖

這裏寫圖片描述

這還只是列出了一部分的類,這簡直是類爆炸,能夠看出這樣作顯然是不行的。因此咱們要慎用繼承,儘可能用組合和委託。

2.4 裝飾者模式登場

裝飾者模式涉及到的一個重要的設計原則 (固然還涉及到了其餘的設計原則,好比多用組合,少用繼承等):類應該對擴展開放,對修改關閉。

在設計過程當中,咱們容許類容易擴展,在不修改原有代碼的狀況下,就能夠擴展新的行爲。這樣的設計具備彈性能夠應對改變,能夠接受新的功能來應對新的需求。

將裝飾者模式應用到問題中去:假如咱們想要摩卡和豆漿深焙咖啡,那麼,要作的是:

  1. 拿一個深焙咖啡 (DarkRoast) 對象
  2. 以摩卡 (Mocha) 裝飾它
  3. 以豆漿 (Soy) 裝飾它
  4. 調用 cost() 方法,並依賴委託將調料的價錢加上去

這裏寫圖片描述

(1) 裝飾者模式設計圖
這裏寫圖片描述

(2)代碼實現

飲料 Beverage 抽象類 (抽象構件)

package com.jas.decorator;

public abstract class Beverage {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

濃咖啡 Espresso 類 (具體構件)

package com.jas.decorator;

public class Espresso extends Beverage {
    public Espresso(){
        description = "Espresso ";
    }
    
    @Override
    public double cost() {
        return 1.99;
    }
}

黑咖啡 HouseBlend 類 (具體構件)

package com.jas.decorator;

public class HouseBlend extends Beverage {
    public HouseBlend(){
        description = "House Blend Coffee ";
    }
    
    @Override
    public double cost() {
        return 0.80;
    }
}

調料 CondimentDecorator 抽象類 (抽象裝飾構件)

package com.jas.decorator;

public abstract class CondimentDecorator extends Beverage{
    @Override
    public abstract String getDescription();
}

摩卡 Mocha 類 (具體裝飾構件)

package com.jas.decorator;

public class Mocha extends CondimentDecorator {
    private Beverage beverage = null;    //用一個實例變量來記錄飲料,也就是被裝飾者
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;    //經過構造函數將被裝飾者實例化
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha ";    //用來加上調料,一塊兒描述飲料
    }

    @Override
    public double cost() {
        return 0.2 + beverage.cost();    //計算摩卡飲料的價錢,爲摩卡價錢 + 飲料價錢
    }
}

豆漿 Soy 類 (具體裝飾構件)

package com.jas.decorator;

public class Soy extends CondimentDecorator {
    private Beverage beverage = null;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Soy ";
    }

    @Override
    public double cost() {
        return 0.1 + beverage.cost();
    }
}

測試代碼 StarbuzzCoffee

package com.jas.decorator;

public class StarbuzzCoffee {
    public static void main(String[] args) {
        
        //簡單要一杯濃咖啡
        Beverage beverage1 = new Espresso();
        System.out.println(beverage1.getDescription() + "$" +  beverage1.cost());
        
        //兩份摩卡加一份豆漿的濃咖啡
        Beverage beverage2 = new Espresso();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Soy(beverage2);
        System.out.println(beverage2.getDescription() + "$" + beverage2.cost());
        
        //一份摩卡加一份豆漿的黑咖啡
        Beverage beverage3 = new HouseBlend();
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println(beverage3.getDescription() + "$" +  beverage3.cost());
    }
}

    /**
     * 輸出
     * Espresso $1.99
     * Espresso , Mocha , Mocha , Soy $2.49
     * House Blend Coffee , Mocha , Soy $1.1
     */

2.5 裝飾者模式問題總結

  • 裝飾者與被裝飾對象有相同的超類型 (DarkRoast 與裝飾類 MochaSoy 都繼承自 Beverage(飲料))。
  • 可使用一個或多個裝飾對象包裝一個對象。
  • 由於裝飾者與被裝飾者具備相同的超類型,因此在任何須要原始對象的狀況下,均可以用裝飾過的對象去代替它。
  • 裝飾者能夠在所委託被裝飾者的行爲以前與以後,加上本身的行爲,以達到特定的目的。
  • 對象能夠在任什麼時候候被裝飾,因此在運行時動態地、不限量地用你喜歡的裝飾者去裝飾對象。

3、真實世界的裝飾者 Java I/O

3.1 瞭解 Java I/O 裝飾者模式

在瞭解了裝飾者模式以後,I/O 相關的類對你來講就更有意義了,由於這其中不少類都是裝飾者。好比下面相關的類
這裏寫圖片描述

裝飾 I/O 類
這裏寫圖片描述

3.2 自定義 Java I/O 裝飾者

問題描述:讀取文件,把輸入流內的全部大寫字符轉爲小寫。

裝飾者 LowerCaseInputStream

package com.jas.decorator;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 擴展 FilterInputStream,這是全部 InputStream 的抽象裝飾者
 */
public class LowerCaseInputStream extends FilterInputStream {
    
    public LowerCaseInputStream(InputStream inputStream){
        super(inputStream);
    }
    
    @Override
    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }
    
    @Override
    public int read(byte[] bytes, int offset, int len) throws IOException{
        int result = super.read(bytes, offset, len);
        for (int i = offset; i < offset + result; i++) {
            bytes[i] = (byte) Character.toLowerCase((char)bytes[i]);
        }
        return result;
    }
}

測試 InputTest

package com.jas.decorator;

import java.io.*;

public class InputTest {
    public static void main(String[] args) {
        
        int c = 0;
        InputStream in = null;

        try {
            //設置 FileInputStream ,先用 BufferedInputStream 裝飾它,再用 LowerCaseInputStream 進行裝飾
            in = new LowerCaseInputStream(
                    new BufferedInputStream(
                            new FileInputStream("test.txt")));
            while ((c = in.read()) >= 0){
                System.out.print((char)c);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

     /**在文件中爲「HELLO WORLD」
     *
     * 輸出
     * hello world
     */

4、裝飾者模式總結

4.1 裝飾者模式的優缺點

優勢

  1. Decorator 模式與繼承關係的目的都是要擴展對象的功能,可是 Decorator 能夠提供比繼承更多的靈活性。
  2. 經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,設計者能夠創造出不少不一樣行爲的組合。

缺點

  1. 這種比繼承更加靈活機動的特性,也同時意味着更加多的複雜性。
  2. 裝飾模式會致使設計中出現許多小類 (I/O 類中就是這樣),若是過分使用,會使程序變得很複雜。
  3. 裝飾模式是針對抽象組件(Component)類型編程。可是,若是你要針對具體組件編程時,就應該從新思考你的應用架構,以及裝飾者是否合適。

參考資料

《Head First 設計模式》

相關文章
相關標籤/搜索