Java設(shè)計(jì)模式中的裝飾器模式簡(jiǎn)析
1. 什么是裝飾器模式
裝飾模式能夠?qū)崿F(xiàn)動(dòng)態(tài)的為對(duì)象添加功能,是從一個(gè)對(duì)象外部來(lái)給對(duì)象添加功能。通常給對(duì)象添加功能,要么直接修改對(duì)象添加相應(yīng)的功能,要么派生對(duì)應(yīng)的子類來(lái)擴(kuò)展,抑或是使用對(duì)象組合的方式。顯然,直接修改對(duì)應(yīng)的類這種方式并不可取。在面向?qū)ο蟮脑O(shè)計(jì)中,而我們也應(yīng)該盡量使用對(duì)象組合,而不是對(duì)象繼承來(lái)擴(kuò)展和復(fù)用功能。裝飾器模式就是基于對(duì)象組合的方式,可以很靈活的給對(duì)象添加所需要的功能。裝飾器模式的本質(zhì)就是動(dòng)態(tài)組合。動(dòng)態(tài)是手段,組合才是目的。總之,裝飾模式是通過(guò)把復(fù)雜的功能簡(jiǎn)單化,分散化,然后再運(yùn)行期間,根據(jù)需要來(lái)動(dòng)態(tài)組合的這樣一個(gè)模式。
注意上文說(shuō)的兩點(diǎn),簡(jiǎn)單化,動(dòng)態(tài)組合。
適用裝飾者模式場(chǎng)合:
1.當(dāng)我們需要為某個(gè)現(xiàn)有的對(duì)象,動(dòng)態(tài)的增加一個(gè)新的功能或職責(zé)時(shí),可以考慮使用裝飾模式。
2.當(dāng)某個(gè)對(duì)象的職責(zé)經(jīng)常發(fā)生變化或者經(jīng)常需要?jiǎng)討B(tài)的增加職責(zé),避免為了適應(yīng)這樣的變化,而增加繼承子類擴(kuò)展的方式,因?yàn)檫@種方式會(huì)造成子類膨脹的速度過(guò)快,難以控制。
簡(jiǎn)單來(lái)說(shuō),就是在添加功能的情況下,又不失靈活,比如上面說(shuō)到的生成Excel模板,如果以后想把鏈接頁(yè)放到內(nèi)容頁(yè)前面,那么只需要調(diào)整一下組合的順序,就可以實(shí)現(xiàn)了,不用把它的實(shí)現(xiàn)代碼大段地拷貝過(guò)去。
2.裝飾器的結(jié)構(gòu)
抽象構(gòu)件(component)角色 :這個(gè)角色用來(lái)規(guī)范被裝飾的對(duì)象,一般用接口方式給出。
具體構(gòu)件(concrete component )角色 :被裝飾的類。
裝飾(decorator)角色 :持有一個(gè)構(gòu)件對(duì)象的實(shí)例。并定義一個(gè)跟抽象構(gòu)件一致的接口。
具體 (concrete decorator ) 裝飾角色 :負(fù)責(zé)給具體構(gòu)件添加附加職責(zé)的類。在實(shí)際使用中多數(shù)情況下裝飾角色和具體裝飾角色可能由一個(gè)類來(lái)承擔(dān)。
這個(gè)結(jié)構(gòu)中最關(guān)鍵的是,裝飾角色持有一個(gè)構(gòu)件對(duì)象的實(shí)例。這樣,需要裝飾的實(shí)例,才能夠傳入到裝飾器中,讓裝飾器對(duì)其進(jìn)行裝飾。同時(shí),在多個(gè)裝飾器共同裝飾的情況下,還可以把前面的裝飾器傳入到后面的裝飾器中,由最后的裝飾器調(diào)用動(dòng)作。因?yàn)樗鼈儗?shí)現(xiàn)了同樣的接口,這樣做是允許的。
例如:對(duì)象A,需要裝飾器A,B進(jìn)行裝飾。那么,可以把A傳給裝飾器A,裝飾后,再把A傳給裝飾器B,繼續(xù)裝飾。也可以把A傳給裝飾器A之后,再把裝飾器A傳給裝飾器B,由B完成所有的裝飾動(dòng)作(實(shí)際上只是調(diào)用了A的裝飾動(dòng)作,具體實(shí)現(xiàn)仍是在裝飾器A當(dāng)中)。
有同學(xué)可能會(huì)有疑問(wèn)了,使用裝飾器模式,要求被裝飾的類型必須和裝飾器的類型,實(shí)現(xiàn)相同的接口,具有相同的公有方法。對(duì)于已經(jīng)定義好的類型,怎么能做到裝飾呢?
比如上面說(shuō)的場(chǎng)景,對(duì)Excel文件進(jìn)行裝飾,一般我們使用POI的開源包,Excel文件對(duì)應(yīng)HSSFWorkBook類型,那么,是不是我們也要去實(shí)現(xiàn)HSSFWorkBook實(shí)現(xiàn)的接口WorkBook?那WorkBook接口里面沒(méi)有我們想要的裝飾方法聲明怎么辦?不就用不了了?
實(shí)際上這種場(chǎng)景,仍然可以使用裝飾器模式,方法就是將WorkBook類型封裝到具體構(gòu)件的角色里,并提供get方法。這樣裝飾器得到具體構(gòu)件后,就可以通過(guò)get方法獲取到真正需要裝飾的對(duì)象了(在下面的例子中,我將使用StringBuilder類型,原理是一樣的)
3. 一個(gè)小例子
嗯,是不是看懵逼了?沒(méi)關(guān)系,我們用上面的生成模板的場(chǎng)景,實(shí)踐一下。
首先,定義一個(gè)抽象構(gòu)件
package com.khlin.test; /** * 模板文件類型。包裝了文件的內(nèi)容,{@link #fillContent()} 用于填充內(nèi)容 * @author Kingsley * */ public interface TemplateFile { StringBuilder getContent(); void fillContent(); }
定義具體構(gòu)件,即被裝飾的類
package com.khlin.test; public class ImportTemplateFile implements TemplateFile { StringBuilder content = new StringBuilder(); public ImportTemplateFile() { content.append("Title: this is an import template."); } @Override public StringBuilder getContent() { return this.content; } @Override public void fillContent() { System.out.println("ImportTemplateFile: i will do nothing."); } }
第三步,定義一個(gè)裝飾器角色。通常是一個(gè)抽象類,同時(shí)持有第一步抽象構(gòu)件的一個(gè)實(shí)例,這是關(guān)鍵。
package com.khlin.test; public abstract class FileDecorator implements TemplateFile { TemplateFile templateFile; public FileDecorator(TemplateFile templateFile) { this.templateFile = templateFile; } }
最后一步,實(shí)現(xiàn)兩個(gè)具體的裝飾器
package com.khlin.test; public class FileAutherDecorator extends FileDecorator { public FileAutherDecorator(TemplateFile templateFile) { super(templateFile); } @Override public StringBuilder getContent() { return this.templateFile.getContent(); } @Override public void fillContent() { //先用上一個(gè)裝飾器處理,再用自己的邏輯處理 this.templateFile.fillContent(); StringBuilder content = this.templateFile.getContent(); content.append("\r\nAuther: kingsley"); } }
package com.khlin.test; import java.util.Date; public class FileDateDecorator extends FileDecorator{ public FileDateDecorator(TemplateFile templateFile) { super(templateFile); } @Override public StringBuilder getContent() { return templateFile.getContent(); } @Override public void fillContent() { //先用上一個(gè)裝飾器處理,再用自己的邏輯處理 this.templateFile.fillContent(); StringBuilder content = this.templateFile.getContent(); content.append("\r\nDate: " + new Date(System.currentTimeMillis())); } }
最后,我們來(lái)運(yùn)行一下
package com.khlin.test; public class App { public static void main(String[] args) { TemplateFile file = new ImportTemplateFile(); // 把要裝飾的對(duì)象傳給裝飾器 TemplateFile dateDecorator = new FileDateDecorator(file); /** * 這里可以有兩種做法。 * 第一種是先調(diào)用dateDecorator.fillContent(),先進(jìn)行裝飾,然后再把file傳給autherDecorator * ,由它繼續(xù)裝飾。 第二種是把dateDecorator作為參數(shù)傳給autherDecorator,由后者一次性地全部裝飾。 * 這里采用第二種。選用這種,要保證在裝飾器的裝飾方法里面,顯式地調(diào)用傳入?yún)?shù)的裝飾方法。 */ TemplateFile autherDecorator = new FileAutherDecorator(dateDecorator); autherDecorator.fillContent(); System.out.println(autherDecorator.getContent()); } }
實(shí)例:
漢堡基類
package decorator; public abstract class Humburger { protected String name ; public String getName(){ return name; } public abstract double getPrice(); }
雞腿堡類
package decorator; public class ChickenBurger extends Humburger { public ChickenBurger(){ name = "雞腿堡"; } @Override public double getPrice() { return 10; } }
配料的基類
package decorator; public abstract class Condiment extends Humburger { public abstract String getName(); }
生菜
package decorator; public class Lettuce extends Condiment { Humburger humburger; public Lettuce(Humburger humburger){ this.humburger = humburger; } @Override public String getName() { return humburger.getName()+" 加生菜"; } @Override public double getPrice() { return humburger.getPrice()+1.5; } }
辣椒
package decorator; public class Chilli extends Condiment { Humburger humburger; public Chilli(Humburger humburger){ this.humburger = humburger; } @Override public String getName() { return humburger.getName()+" 加辣椒"; } @Override public double getPrice() { return humburger.getPrice(); //辣椒是免費(fèi)的哦 } }
測(cè)試
package decorator; public class Test { /** * @param args */ public static void main(String[] args) { Humburger humburger = new ChickenBurger(); System.out.println(humburger.getName()+" 價(jià)錢:"+humburger.getPrice()); Lettuce lettuce = new Lettuce(humburger); System.out.println(lettuce.getName()+" 價(jià)錢:"+lettuce.getPrice()); Chilli chilli = new Chilli(humburger); System.out.println(chilli.getName()+" 價(jià)錢:"+chilli.getPrice()); Chilli chilli2 = new Chilli(lettuce); System.out.println(chilli2.getName()+" 價(jià)錢:"+chilli2.getPrice()); } }
輸出
雞腿堡 價(jià)錢:10.0
雞腿堡 加生菜 價(jià)錢:11.5
雞腿堡 加辣椒 價(jià)錢:10.0
雞腿堡 加生菜 加辣椒 價(jià)錢:11.5
到此這篇關(guān)于Java設(shè)計(jì)模式中的裝飾器模式簡(jiǎn)析的文章就介紹到這了,更多相關(guān)Java裝飾器模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java返回json請(qǐng)求中文變成問(wèn)號(hào)的問(wèn)題及解決
這篇文章主要介紹了java返回json請(qǐng)求中文變成問(wèn)號(hào)的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07設(shè)計(jì)模式系列之組合模式及其在JDK和MyBatis源碼中的運(yùn)用詳解
這篇文章主要介紹了組合模式及其在JDK和MyBatis源碼中的運(yùn)用,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Spring Cloud服務(wù)入口Gateway的介紹和使用問(wèn)題小結(jié)
Spring Cloud Gateway是Spring Cloud的?個(gè)全新的API?關(guān)項(xiàng)?, 基于Spring + SpringBoot等技術(shù)開發(fā), ?的是為了替換掉Zuul,這篇文章主要介紹了Spring Cloud服務(wù)入口Gateway的介紹和使用問(wèn)題小結(jié),需要的朋友可以參考下2025-03-03java發(fā)送email一般步驟(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇java發(fā)送email一般步驟(實(shí)例講解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09IntelliJ IDEA 刷題利器 LeetCode 插件詳解
這篇文章主要介紹了IntelliJ IDEA 刷題利器 LeetCode 插件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08