1. 程式人生 > >設計模式--裝飾者模式(在IO體系中的應用)

設計模式--裝飾者模式(在IO體系中的應用)

上一篇介紹了介面卡模式,它是將一個類的介面,轉化成客戶期望的另一個介面,介面卡讓原本介面不相容的類可以合作無間。裝飾者模式:動態的將責任附加到物件上(因為利用組合而不是繼承來實現,而組合是可以在執行時進行隨機組合的)用來擴充套件功能。若要擴充套件功能,裝飾者提供了比繼承更富有彈性的替代方案(同樣地,通過組合可以很好的避免類暴漲,也規避了繼承中的子類必須無條件繼承父類所有屬性的弊端)。我們的目標是允許類統一擴充套件,在不修改現有程式碼的情況下,就可搭配新的行為且符合開閉原則。對於裝飾者模式,JDK中典型的應用莫過於是IO體系。

裝飾者模式

其特點是:
裝飾者類擁有被裝飾者類的物件,一般是當構造引數傳入。
在裝飾者類當中呼叫被裝飾者類的方法,封裝成新的功能方法。
裝飾者設計模式主要是利用多型,將子類物件作為引數互相傳遞(主要為了傳遞實現的函式),達到互相裝飾的效果,從而減少程式碼重複率,優化程式碼結構。
這裡寫圖片描述


與component是實現和聚合關係 (aggregation)。

//.Component : 定義一個物件介面,可以給這些物件動態地新增職責。
interface Component {
     public void operation();
}
// ConcreteComponent : 實現 Component 定義的介面。
class ConcreteComponent implements Component {
    @Override
    public void operation() {
        System.out.println("初始行為");
    }
}

Decorator : 裝飾抽象類,繼承了 Component, 從外類來擴充套件 Component 類的功能,但對於 Component 來說,是無需知道 Decorator 的存在的。

class Decorator implements Component {
    // 維護一個 Component 物件,和 Component 形成聚合關係
    protected Component component;
    // 傳入要進一步修飾的物件
    public Decorator(Component component) {
        this.component = component;
    }
    @Override
    // 呼叫要修飾物件的原方法
public void operation() { component.operation(); } }

ConcreteDecorator : 具體的裝飾物件,起到給 Component 新增職責的功能。

class ConcreteDecoratorA extends Decorator {
    private String addedState = "新屬性1";

    public ConcreteDecoratorA(Component component) {
        super(component);
    }
//對原方法進行了增強
    public void operation() {
       super.operation();
       System.out.println("新增屬性: " + addedState);

    }
}

class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
     super(component);
  }

    public void operation() {
       super.operation();
       AddedBehavior();
     }

    public void AddedBehavior() {
       System.out.println("新增行為");
   }

}

呼叫程式碼是

public class DecoratorPattern {

    public static void main(String[] args) {

        Component component = new ConcreteComponent();

        component.operation();

        System.out.println("======================================");

        Decorator decoratorA = new ConcreteDecoratorA(component);

        decoratorA.operation();

        System.out.println("======================================");

        Decorator decoratorB = new ConcreteDecoratorB(decoratorA);

        decoratorB.operation();
  }

}

其應用場景為:
a.需要動態的、透明的為一個物件新增職責,即不影響其他物件。
b.需要動態的給一個物件新增功能,這些功能可以再動態的撤銷。
c.需要增加由一些基本功能的排列組合而產生的非常大量的功能,從而使繼承關係變的不現實。
d.當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。

與代理模式的區別

上邊介紹的裝飾者模式對類進行擴充套件增強,我們會思考,這和代理模式有些類似,都是對類功能進行增強。這和aop有些類似,aop是由代理模式實現的,且對於JDK動態代理,其代理類和被代理類也必須得實現同一個介面,其區別在哪呢?
我們可以先看這兩種設計模式的定義:
代理模式(Proxy Pattern),為其它物件提供一種代理以控制對這個物件的訪問。
裝飾模式(Decorator Pattern),動態地給一個物件新增一些額外的職責。
換句話說:代理模式的目標是控制對被代理物件的訪問,而裝飾模式是給原物件增加額外功能。雖然代理模式也可以實現對被代理物件功能的增強,但其核心是隱藏對被代理類的訪問。
這裡寫圖片描述
從uml圖中也可以看出:當使用代理模式的時候,我們常常在一個代理類中建立一個物件的例項。並且,當我們使用裝飾器模式的時候,我們通常的做法是將原始物件作為一個引數傳給裝飾者的構造器。裝飾器模式關注於在一個物件上動態的新增方法,然而代理模式關注於控制對物件的訪問。
我們可以用另外一句話來總結這些差別:使用代理模式,代理和真實物件之間的的關係通常在編譯時就已經確定了,而裝飾者能夠在執行時遞迴地被構造。
代理類所能代理的類完全由代理類確定,裝飾類裝飾的物件需要根據實際使用時客戶端的組合來確定被代理物件由代理物件建立,客戶端甚至不需要知道被代理類的存在;被裝飾物件由客戶端建立並傳給裝飾物件。我們可以動態組合裝飾物件。

裝飾者模式在Java IO體系中的應用

java 的IO體系很好地應用了裝飾者模式,下邊我們可以先看一下Java IO流體系。
這裡寫圖片描述
從圖中可以看出,InputStream就是裝飾者模式中的超類(Component),ByteArrayInputStream,FileInputStream相當於被裝飾者(ConcreteComponent),這些類都提供了最基本的位元組讀取功能。
而另外一個和這兩個類是同一級的類FilterInputStream即是裝飾者(Decorator),BufferedInputStream,DataInputStream,PushbackInputStream(都繼承了FilterInputStream類)…這些都是被裝飾者裝飾後形成的成品。
根據裝飾者模式的特點,我們可以總結出這些IO流的使用方法:

File file = new File ("hello.txt"); 
FileInputStream in=new FileInputStream(file); 
BufferedInputStream inBuffered=new BufferedInputStream (in); 

這裡BufferedInputStream主要是提供了快取機制,先讀入一個byte[],等count到達快取Byte[]的大小的時候,再一次讀入。 當然你也可以寫成:

BufferedInputStream inBuffered = 
        new BufferedInputStream (new FileInputStream(new File ("hello.txt"))); 

從使用的角度來看裝飾者模式,可以看出它的一個缺點:裝飾者模式的實現對於使用者是透明的,當使用者不熟悉你的實現的時,就很難理解。 同理你可以學習一下另外一個結構outputStream 。

介面卡模式比較簡單就不多講了,主要是解決了java無法多繼承的問題,下面大概講一下IO包中是怎麼用這個模式的,用它來做什麼? InputStreamReader和InputStream的功能的不同點在於InputStream是以二進位制輸入 / 輸出, I/O 速度快且效率高,由於讀到的是位元組,也就不存在亂碼問題,平臺移植性好。但是它的 read ()方法讀到的是一個位元組,很不利於人們閱讀。InputStreamReader類將位元組轉換為字元。 你可以在構造器中指定編碼的方式,如果不指定的話將採用底層作業系統的預設編碼方式。
在字元流中:

File file = new File ("hello.txt");   
FileInputStream in=new FileInputStream(file); 
InputStreamReader inReader=new InputStreamReader(in); 
BufferedReader bufReader=new BufferedReader(inReader); 

可以看出步驟2到3使用的是介面卡模式,而3到4使用的是裝飾者模式 。