【源码分析设计模式 5】Java I/O系统中的装饰器模式
一、基本介紹
動態地將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
二、裝飾器模式的結構
1、Component,抽象構件
Component是一個接口或者抽象類,是定義我們最核心的對象,也可以說是最原始的對象,比如街邊小吃;
2、ConcreteComponent,具體構件,或者基礎構件
ConcreteComponent是最核心、最原始、最基本的接口或抽象類Component的實現,可以單獨用,也可將其進行裝飾,比如街邊小吃最有名的手抓餅;
3、Decorator,裝飾角色
一般是一個抽象類,繼承自或實現Component,在它的屬性里面有一個變量指向Component抽象構件,我覺得這是裝飾器最關鍵的地方。
4、ConcreteDecorator,具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個具體的裝飾類,它們可以把基礎構件裝飾成新的東西,比如把一個普通的手抓餅裝飾成加蛋、加腸兒、金針菇的手抓餅。
三、裝飾器模式優缺點
1、優點
(1)裝飾類和被裝飾類可以獨立發展,而不會相互耦合。換句話說,Component類無需知道Decorator類,Decorator類是從外部來擴展Component類的功能,而Decorator也不用知道具體的構件。
(2)裝飾器模式是繼承關系的一個替代方案。我們看裝飾類Decorator,不管裝飾多少層,返回的對象還是Component(因為Decorator本身就是繼承自Component的),實現的還是is-a的關系。
2、缺點
(1)裝飾器模式雖然減少了類的爆炸,但是在使用的時候,你就可能需要更多的對象來表示繼承關系中的一個對象
(2)裝飾器模式雖然從數量級上減少了類的數量,但是為了要裝飾,仍舊會增加很多的小類這些具體的裝飾類的邏輯將不會非常的清晰,不夠直觀,容易令人迷惑。
(3)多層的裝飾是比較復雜的。為什么會復雜?你想想看,就像剝洋蔥一樣,你剝到最后才發現是最里層的裝飾出現了問題,可以想象一下工作量。這點從我使用Java I/O的類庫就深有感受,我只需要單一結果的流,結果卻往往需要創建多個對象,一層套一層,對于初學者來說容易讓人迷惑。
四、裝飾器模式的使用場景
1、當你想要給一個類增加功能,然而,卻并不想修改原來類的代碼時,可以考慮裝飾器模式如果你想要動態的給一個類增加功能,并且這個功能你還希望可以動態的撤銷,就好像直接拿掉了一層裝飾物;
2、比如java里面的基本數據類型int、boolean、char....都有它們對應的裝飾類Integer、Boolean、Character....
3、在Java IO中,具體構建角色是節點流、裝飾角色是過濾流;
FilterInputStream和FilterOutputStream是裝飾角色,而其他派生自它們的類則是具體裝飾角色。
DataoutputStream out=new DataoutputStream(new FileoutputStream());
這就是 裝飾者模式,DataoutputStream是裝飾者子類,FileoutputStream是實現接口的子類。
這里不會調用到裝飾者類--FilteroutputStream,只是作為繼承的另一種方案,對客戶端來說是透明的,是為了功能的擴張。
五、裝飾器模式實現手抓餅
老板,來個手抓餅,加個蛋、加根烤腸多少錢?
這個就是裝飾器模式,用蛋和烤腸去裝飾手抓餅,讓手抓餅更加美味。
1、Component,抽象構件:街邊小吃
package designMode.advance.decorator;public abstract class Snack {public String des; // 描述private float price = 0.0f;public String getDes() {return des;}public void setDes(String des) {this.des = des;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}//計算費用的抽象方法//子類來實現public abstract float cost(); }2、ConcreteComponent,具體構件,或者基礎構件
(1)手抓餅
package designMode.advance.decorator;public class HandGrabCake extends Snack {public HandGrabCake() {setPrice(5.0f);setDes(" 手抓餅 "+cost());}@Overridepublic float cost() {return super.getPrice();} }(2)烤冷面
package designMode.advance.decorator;public class GrilledColdNoodles extends Snack {public GrilledColdNoodles() {setPrice(4.0f);setDes(" 烤冷面 "+cost());}@Overridepublic float cost() {return super.getPrice();} }Decorator,裝飾角色
package designMode.advance.decorator;public class Decorator extends Snack {private Snack obj;public Decorator(Snack obj) { //組合this.obj = obj;}@Overridepublic float cost() {return super.getPrice() + obj.cost();}@Overridepublic String getDes() {// obj.getDes() 輸出被裝飾者的信息return des + " " + getPrice() + " && " + obj.getDes();} }3、具體裝飾角色
(1)雞蛋
package designMode.advance.decorator;public class Egg extends Decorator {public Egg(Snack obj) {super(obj);setDes(" 雞蛋 ");setPrice(1.0f);} }(2)烤腸
package designMode.advance.decorator;public class Sausage extends Decorator {public Sausage(Snack obj) {super(obj);setDes(" 烤腸 ");setPrice(2.0f);} }(3)金針菇
package designMode.advance.decorator;public class NeedleMushroom extends Decorator{public NeedleMushroom(Snack obj) {super(obj);setDes(" 金針菇 ");setPrice(2.5f);} }4、老板,來個手抓餅,加2個蛋、加1根烤腸
package designMode.advance.decorator;public class HandGrabCakeBar {public static void main(String[] args) {// 裝飾者模式下的訂單:2個蛋+一根烤腸的手抓餅// 1. 點一份手抓餅Snack order = new HandGrabCake();System.out.println("小白手抓餅費用=" + order.cost());System.out.println("描述=" + order.getDes());// 2. order 加入一個雞蛋order = new Egg(order);System.out.println("手抓餅 加入1個雞蛋 費用 =" + order.cost());System.out.println("手抓餅 加入1個雞蛋 描述 = " + order.getDes());// 3. order 加入一個雞蛋order = new Egg(order);System.out.println("手抓餅 加入1個雞蛋 加入2個雞蛋 費用 =" + order.cost());System.out.println("手抓餅 加入1個雞蛋 加入2個雞蛋 描述 = " + order.getDes());// 3. order 加入一根烤腸order = new Sausage(order);System.out.println("手抓餅 加入1個雞蛋 加入2個雞蛋 加1根烤腸 費用 =" + order.cost());System.out.println("手抓餅 加入1個雞蛋 加入2個雞蛋 加1根烤腸 描述 = " + order.getDes());System.out.println("===========================");Snack order2 = new GrilledColdNoodles();System.out.println("考冷面 費用 =" + order2.cost());System.out.println("考冷面 描述 = " + order2.getDes());// 1. order2 加入一袋金針菇order2 = new NeedleMushroom(order2);System.out.println("考冷面 加入一袋金針菇 費用 =" + order2.cost());System.out.println("考冷面 加入一袋金針菇 描述 = " + order2.getDes());} }5、好嘞,您拿好
六、裝飾器模式在Java I/O系統中的實現
?前面總結了這么多,再從大神們的作品中找一個實際應用例子吧,畢竟那是經歷實戰檢驗的,肯定是有道理的。嗯,在平時的留意中我發現Java I/O系統的設計中用到了這一設計模式,因為Java I/O類庫需要多種不同功能的組合。這里我就以InputStream為例簡單說明一下,同樣我們還是來看一下其類圖:
InputStream作為抽象構件,其下面大約有如下幾種具體基礎構件,從不同的數據源產生輸入:
- ByteArrayInputStream,從字節數組產生輸入;
- FileInputStream,從文件產生輸入;
- StringBufferInputStream,從String對象產生輸入;
- PipedInputStream,從管道產生輸入;
- SequenceInputStream,可將其他流收集合并到一個流內;
?FilterInputStream作為裝飾器在JDK中是一個普通類,其下面有多個具體裝飾器比如BufferedInputStream、DataInputStream等。我們以BufferedInputStream為例,使用它就是避免每次讀取時都進行實際的寫操作,起著緩沖作用。我們可以在這里稍微深入一下,站在源碼的角度來管中窺豹。
FilterInputStream內部封裝了基礎構件:
protected volatile InputStream in;而BufferedInputStream在調用其read()讀取數據時會委托基礎構件來進行更底層的操作,而它自己所起的裝飾作用就是緩沖,在源碼中可以很清楚的看到這一切:
public synchronized int read() throws IOException {if (pos >= count) {fill();if (pos >= count)return -1;}return getBufIfOpen()[pos++] & 0xff; }private void fill() throws IOException {byte[] buffer = getBufIfOpen();if (markpos < 0)pos = 0; /* no mark: throw away the buffer */else if (pos >= buffer.length) /* no room left in buffer */if (markpos > 0) { /* can throw away early part of the buffer */int sz = pos - markpos;System.arraycopy(buffer, markpos, buffer, 0, sz);pos = sz;markpos = 0;} else if (buffer.length >= marklimit) {markpos = -1; /* buffer got too big, invalidate mark */pos = 0; /* drop buffer contents */} else if (buffer.length >= MAX_BUFFER_SIZE) {throw new OutOfMemoryError("Required array size too large");} else { /* grow buffer */int nsz = (pos <= MAX_BUFFER_SIZE - pos) ?pos * 2 : MAX_BUFFER_SIZE;if (nsz > marklimit)nsz = marklimit;byte nbuf[] = new byte[nsz];System.arraycopy(buffer, 0, nbuf, 0, pos);if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {throw new IOException("Stream closed");}buffer = nbuf;}count = pos;// 看這行就行了,委托基礎構件來進行更底層的操作int n = getInIfOpen().read(buffer, pos, buffer.length - pos);if (n > 0)count = n + pos; }private InputStream getInIfOpen() throws IOException {InputStream input = in;if (input == null)throw new IOException("Stream closed");return input; }這部分的代碼很多,這里我們沒有必要考慮這段代碼的具體邏輯,只需要看到在BufferedInputStream的read方法中通過getInIfOpen()獲取基礎構件從而委托其進行更底層的操作(在這里是讀取單個字節)就可以說明本文所要說的一切了。
至于I/O類庫中的其他設計諸如OutputStream、Writer、Reader,是一致的,這里就不再贅述了。
?
總結
以上是生活随笔為你收集整理的【源码分析设计模式 5】Java I/O系统中的装饰器模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Boost(2):boost.pytho
- 下一篇: java计算机毕业设计旅游服务平台源代码