【源码分析设计模式 5】Java I/O系统中的装饰器模式
一、基本介紹
動(dòng)態(tài)地將責(zé)任附加到對象上。若要擴(kuò)展功能,裝飾者提供了比繼承更有彈性的替代方案。
二、裝飾器模式的結(jié)構(gòu)
1、Component,抽象構(gòu)件
Component是一個(gè)接口或者抽象類,是定義我們最核心的對象,也可以說是最原始的對象,比如街邊小吃;
2、ConcreteComponent,具體構(gòu)件,或者基礎(chǔ)構(gòu)件
ConcreteComponent是最核心、最原始、最基本的接口或抽象類Component的實(shí)現(xiàn),可以單獨(dú)用,也可將其進(jìn)行裝飾,比如街邊小吃最有名的手抓餅;
3、Decorator,裝飾角色
一般是一個(gè)抽象類,繼承自或?qū)崿F(xiàn)Component,在它的屬性里面有一個(gè)變量指向Component抽象構(gòu)件,我覺得這是裝飾器最關(guān)鍵的地方。
4、ConcreteDecorator,具體裝飾角色
ConcreteDecoratorA和ConcreteDecoratorB是兩個(gè)具體的裝飾類,它們可以把基礎(chǔ)構(gòu)件裝飾成新的東西,比如把一個(gè)普通的手抓餅裝飾成加蛋、加腸兒、金針菇的手抓餅。
三、裝飾器模式優(yōu)缺點(diǎn)
1、優(yōu)點(diǎn)
(1)裝飾類和被裝飾類可以獨(dú)立發(fā)展,而不會(huì)相互耦合。換句話說,Component類無需知道Decorator類,Decorator類是從外部來擴(kuò)展Component類的功能,而Decorator也不用知道具體的構(gòu)件。
(2)裝飾器模式是繼承關(guān)系的一個(gè)替代方案。我們看裝飾類Decorator,不管裝飾多少層,返回的對象還是Component(因?yàn)镈ecorator本身就是繼承自Component的),實(shí)現(xiàn)的還是is-a的關(guān)系。
2、缺點(diǎn)
(1)裝飾器模式雖然減少了類的爆炸,但是在使用的時(shí)候,你就可能需要更多的對象來表示繼承關(guān)系中的一個(gè)對象
(2)裝飾器模式雖然從數(shù)量級上減少了類的數(shù)量,但是為了要裝飾,仍舊會(huì)增加很多的小類這些具體的裝飾類的邏輯將不會(huì)非常的清晰,不夠直觀,容易令人迷惑。
(3)多層的裝飾是比較復(fù)雜的。為什么會(huì)復(fù)雜?你想想看,就像剝洋蔥一樣,你剝到最后才發(fā)現(xiàn)是最里層的裝飾出現(xiàn)了問題,可以想象一下工作量。這點(diǎn)從我使用Java I/O的類庫就深有感受,我只需要單一結(jié)果的流,結(jié)果卻往往需要?jiǎng)?chuàng)建多個(gè)對象,一層套一層,對于初學(xué)者來說容易讓人迷惑。
四、裝飾器模式的使用場景
1、當(dāng)你想要給一個(gè)類增加功能,然而,卻并不想修改原來類的代碼時(shí),可以考慮裝飾器模式如果你想要?jiǎng)討B(tài)的給一個(gè)類增加功能,并且這個(gè)功能你還希望可以動(dòng)態(tài)的撤銷,就好像直接拿掉了一層裝飾物;
2、比如java里面的基本數(shù)據(jù)類型int、boolean、char....都有它們對應(yīng)的裝飾類Integer、Boolean、Character....
3、在Java IO中,具體構(gòu)建角色是節(jié)點(diǎn)流、裝飾角色是過濾流;
FilterInputStream和FilterOutputStream是裝飾角色,而其他派生自它們的類則是具體裝飾角色。
DataoutputStream out=new DataoutputStream(new FileoutputStream());
這就是 裝飾者模式,DataoutputStream是裝飾者子類,FileoutputStream是實(shí)現(xiàn)接口的子類。
這里不會(huì)調(diào)用到裝飾者類--FilteroutputStream,只是作為繼承的另一種方案,對客戶端來說是透明的,是為了功能的擴(kuò)張。
五、裝飾器模式實(shí)現(xiàn)手抓餅
老板,來個(gè)手抓餅,加個(gè)蛋、加根烤腸多少錢?
這個(gè)就是裝飾器模式,用蛋和烤腸去裝飾手抓餅,讓手抓餅更加美味。
1、Component,抽象構(gòu)件:街邊小吃
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;}//計(jì)算費(fèi)用的抽象方法//子類來實(shí)現(xiàn)public abstract float cost(); }2、ConcreteComponent,具體構(gòu)件,或者基礎(chǔ)構(gòu)件
(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、老板,來個(gè)手抓餅,加2個(gè)蛋、加1根烤腸
package designMode.advance.decorator;public class HandGrabCakeBar {public static void main(String[] args) {// 裝飾者模式下的訂單:2個(gè)蛋+一根烤腸的手抓餅// 1. 點(diǎn)一份手抓餅Snack order = new HandGrabCake();System.out.println("小白手抓餅費(fèi)用=" + order.cost());System.out.println("描述=" + order.getDes());// 2. order 加入一個(gè)雞蛋order = new Egg(order);System.out.println("手抓餅 加入1個(gè)雞蛋 費(fèi)用 =" + order.cost());System.out.println("手抓餅 加入1個(gè)雞蛋 描述 = " + order.getDes());// 3. order 加入一個(gè)雞蛋order = new Egg(order);System.out.println("手抓餅 加入1個(gè)雞蛋 加入2個(gè)雞蛋 費(fèi)用 =" + order.cost());System.out.println("手抓餅 加入1個(gè)雞蛋 加入2個(gè)雞蛋 描述 = " + order.getDes());// 3. order 加入一根烤腸order = new Sausage(order);System.out.println("手抓餅 加入1個(gè)雞蛋 加入2個(gè)雞蛋 加1根烤腸 費(fèi)用 =" + order.cost());System.out.println("手抓餅 加入1個(gè)雞蛋 加入2個(gè)雞蛋 加1根烤腸 描述 = " + order.getDes());System.out.println("===========================");Snack order2 = new GrilledColdNoodles();System.out.println("考冷面 費(fèi)用 =" + order2.cost());System.out.println("考冷面 描述 = " + order2.getDes());// 1. order2 加入一袋金針菇order2 = new NeedleMushroom(order2);System.out.println("考冷面 加入一袋金針菇 費(fèi)用 =" + order2.cost());System.out.println("考冷面 加入一袋金針菇 描述 = " + order2.getDes());} }5、好嘞,您拿好
六、裝飾器模式在Java I/O系統(tǒng)中的實(shí)現(xiàn)
?前面總結(jié)了這么多,再從大神們的作品中找一個(gè)實(shí)際應(yīng)用例子吧,畢竟那是經(jīng)歷實(shí)戰(zhàn)檢驗(yàn)的,肯定是有道理的。嗯,在平時(shí)的留意中我發(fā)現(xiàn)Java I/O系統(tǒng)的設(shè)計(jì)中用到了這一設(shè)計(jì)模式,因?yàn)镴ava I/O類庫需要多種不同功能的組合。這里我就以InputStream為例簡單說明一下,同樣我們還是來看一下其類圖:
InputStream作為抽象構(gòu)件,其下面大約有如下幾種具體基礎(chǔ)構(gòu)件,從不同的數(shù)據(jù)源產(chǎn)生輸入:
- ByteArrayInputStream,從字節(jié)數(shù)組產(chǎn)生輸入;
- FileInputStream,從文件產(chǎn)生輸入;
- StringBufferInputStream,從String對象產(chǎn)生輸入;
- PipedInputStream,從管道產(chǎn)生輸入;
- SequenceInputStream,可將其他流收集合并到一個(gè)流內(nèi);
?FilterInputStream作為裝飾器在JDK中是一個(gè)普通類,其下面有多個(gè)具體裝飾器比如BufferedInputStream、DataInputStream等。我們以BufferedInputStream為例,使用它就是避免每次讀取時(shí)都進(jìn)行實(shí)際的寫操作,起著緩沖作用。我們可以在這里稍微深入一下,站在源碼的角度來管中窺豹。
FilterInputStream內(nèi)部封裝了基礎(chǔ)構(gòu)件:
protected volatile InputStream in;而BufferedInputStream在調(diào)用其read()讀取數(shù)據(jù)時(shí)會(huì)委托基礎(chǔ)構(gòu)件來進(jìn)行更底層的操作,而它自己所起的裝飾作用就是緩沖,在源碼中可以很清楚的看到這一切:
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;// 看這行就行了,委托基礎(chǔ)構(gòu)件來進(jìn)行更底層的操作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()獲取基礎(chǔ)構(gòu)件從而委托其進(jìn)行更底層的操作(在這里是讀取單個(gè)字節(jié))就可以說明本文所要說的一切了。
至于I/O類庫中的其他設(shè)計(jì)諸如OutputStream、Writer、Reader,是一致的,這里就不再贅述了。
?
總結(jié)
以上是生活随笔為你收集整理的【源码分析设计模式 5】Java I/O系统中的装饰器模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Boost(2):boost.pytho
- 下一篇: GOJS入门二 -如何节点添加图片