Java設(shè)計(jì)模式之訪問者模式
大多數(shù)情況下你不需要訪問者模式,但當(dāng)一旦需要訪問者模式時(shí),那就是真的需要它了,這是設(shè)計(jì)模式創(chuàng)始人的原話??梢钥闯鰬?yīng)用場(chǎng)景比較少,但需要它的時(shí)候是不可或缺的,這篇文章就開始學(xué)習(xí)最后一個(gè)設(shè)計(jì)模式——訪問者模式。
一、概念理解
訪問者模式概念:封裝作用于某對(duì)象結(jié)構(gòu)中的各元素的操作,它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。
通俗的解釋就是,系統(tǒng)中有一些固定結(jié)構(gòu)的對(duì)象(元素),在其內(nèi)部提供一個(gè)accept()方法用來接受訪問者對(duì)象的訪問,不同的訪問者對(duì)同一元素的訪問內(nèi)容不同,所以使得相同的元素可以產(chǎn)生不同的元素結(jié)果。
比如在一個(gè)人事管理系統(tǒng)中,有多個(gè)工種的員工和多個(gè)老板,不同的老板對(duì)同一個(gè)員工的關(guān)注點(diǎn)是不同的,CTO可能關(guān)注的就是技術(shù),CEO可能更注重績(jī)效。
員工就是一個(gè)穩(wěn)定的元素,老板就是變化的,對(duì)應(yīng)概念就是:封裝員工的一些操作,可以在不改變員工類的前提下,增加新的老板訪問同一個(gè)員工。
在訪問者模式中包含五個(gè)角色,抽象元素、具體元素、抽象訪問者、具體訪問者、結(jié)構(gòu)元素。
抽象元素:定義一個(gè)接受訪問的方法accept,參數(shù)為訪問者對(duì)象。
具體元素:提供接受訪問者訪問的具體實(shí)現(xiàn)調(diào)用訪問者的訪問visit,并定義額外的數(shù)據(jù)操作方法。
抽象訪問者:這個(gè)角色主要是定義對(duì)具體元素的訪問方法visit,理論上來說方法數(shù)等于元素(固定類型的對(duì)象,也就是被訪問者)個(gè)數(shù)。
具體訪問者:實(shí)現(xiàn)對(duì)具體元素的訪問visit方法,參數(shù)就是具體元素。
結(jié)構(gòu)對(duì)象:創(chuàng)建一個(gè)數(shù)組用來維護(hù)元素,并提供一個(gè)方法訪問所有的元素。
二、案例實(shí)現(xiàn)
在一個(gè)公司有干活的工程師和管理者,也有抓技術(shù)的CTO和管績(jī)效的CEO,CTO和CEO都會(huì)訪問管理員和工程師,當(dāng)公司來了新的老板,只需要增加訪問者即可。
工程師和管理者就是元素、公司就是結(jié)構(gòu)體、CEO、CTO就是訪問者。
抽象元素:
/** * 員工 抽象元素 被訪問者 * @author tcy * @Date 29-09-2022 */ public interface ElementAbstract { void accept(VisitorAbstract visitor); }
具體元素-工程師:
/** * 工程師 具體元素 被訪問者 * @author tcy * @Date 29-09-2022 */ public class ElementEngineer implements ElementAbstract { private String name; private int kpi; ElementEngineer(String name){ this.name = name; this.kpi = new Random().nextInt(10); } public String getName() { return name; } public int getKpi() { return kpi; } @Override public void accept(VisitorAbstract visitor) { visitor.visit(this); } public int getCodeLineTotal(){ return this.kpi * 1000000; } }
具體元素-管理者:
/** * 管理者 具體元素 被訪問者 * @author tcy * @Date 29-09-2022 */ public class ElementManager implements ElementAbstract { private String name; private int kpi; ElementManager(String name){ this.name = name; this.kpi = new Random().nextInt(10); } public String getName() { return name; } public int getKpi() { return kpi; } @Override public void accept(VisitorAbstract visitor) { visitor.visit(this); } public int getProductNum(){ return this.kpi * 10; } }
抽象訪問者:
/** * 抽象訪問者 * @author tcy * @Date 29-09-2022 */ public interface VisitorAbstract { void visit(ElementEngineer engineer); void visit(ElementManager manager); }
具體訪問者-CEO
/** * 具體訪問者CEO * @author tcy * @Date 29-09-2022 */ public class VisitorCEO implements VisitorAbstract { @Override public void visit(ElementEngineer engineer) { System.out.println("工程師:" + engineer.getName() + "KPI:" + engineer.getKpi()); } @Override public void visit(ElementManager manager) { System.out.println("經(jīng)理:" + manager.getName() + "KPI:" + manager.getKpi() + " 今年共完成項(xiàng)目:" + manager.getProductNum() + "個(gè)"); } }
具體訪問者-CTO
/** * 具體訪問者CTO * @author tcy * @Date 29-09-2022 */ public class VisitorCTO implements VisitorAbstract { @Override public void visit(ElementEngineer engineer) { System.out.println("工程師:" + engineer.getName() + " 今年代碼量" + engineer.getCodeLineTotal() + "行"); } @Override public void visit(ElementManager manager) { System.out.println("經(jīng)理:" + manager.getName() + " 今年共完成項(xiàng)目:" + manager.getProductNum() + "個(gè)"); } }
結(jié)構(gòu)體:
/** * 結(jié)構(gòu)對(duì)象 * @author tcy * @Date 29-09-2022 */ public class Structure { List<ElementAbstract> list = new ArrayList<>(); public Structure addEmployee(ElementAbstract employee){ list.add(employee); return this; } public void report(VisitorAbstract visitor){ list.forEach(employee -> { employee.accept(visitor); }); } }
客戶端:
/** * @author tcy * @Date 29-09-2022 */ public class Client { public static void main(String[] args) { //元素對(duì)象 ElementEngineer engineerZ = new ElementEngineer("小張"); ElementEngineer engineerW = new ElementEngineer("小王"); ElementEngineer engineerL = new ElementEngineer("小李"); ElementManager managerZ = new ElementManager("張總"); ElementManager managerW = new ElementManager("王總"); ElementManager managerL = new ElementManager("李總"); //結(jié)構(gòu)體對(duì)象 Structure structure = new Structure(); structure.addEmployee(engineerZ).addEmployee(engineerW).addEmployee(engineerL).addEmployee(managerZ).addEmployee(managerW).addEmployee(managerL); structure.report(new VisitorCTO()); System.out.println("---------------------------------------"); structure.report(new VisitorCEO()); } }
訪問者不愧是最難的設(shè)計(jì)模式,方法間的調(diào)用錯(cuò)綜復(fù)雜,日常開發(fā)的使用頻率很低,很多程序員寧可代碼寫的麻煩一點(diǎn)也不用這種設(shè)計(jì)模式,但是作為學(xué)習(xí)者就要學(xué)習(xí)各種設(shè)計(jì)模式了。
三、訪問者模式在JDk中的應(yīng)用
JDK的NIO中的 FileVisitor 接口采用的就是訪問者模式。
在早期的 Java 版本中,如果要對(duì)指定目錄下的文件進(jìn)行遍歷,必須用遞歸的方式來實(shí)現(xiàn),這種方法復(fù)雜且靈活性不高。
Java 7 版本后,F(xiàn)iles 類提供了 walkFileTree() 方法,該方法可以很容易的對(duì)目錄下的所有文件進(jìn)行遍歷,需要 Path、FileVisitor 兩個(gè)參數(shù)。其中,Path 是要遍歷文件的路徑,F(xiàn)ileVisitor 則可以看成一個(gè)文件訪問器,源碼如下。
FileVisitor 主要提供了 4 個(gè)方法,且返回結(jié)果的都是 FileVisitResult 對(duì)象值,用于決定當(dāng)前操作完成后接下來該如何處理。FileVisitResult 是一個(gè)枚舉類,代表返回之后的一些后續(xù)操作,源碼如下。
FileVisitResult 主要包含 4 個(gè)常見的操作。
- FileVisitResult.CONTINUE:這個(gè)訪問結(jié)果表示當(dāng)前的遍歷過程將會(huì)繼續(xù)。
- FileVisitResult.SKIP_SIBLINGS:這個(gè)訪問結(jié)果表示當(dāng)前的遍歷過程將會(huì)繼續(xù),但是要忽略當(dāng)前文件/目錄的兄弟節(jié)點(diǎn)。
- FileVisitResult.SKIP_SUBTREE:這個(gè)訪問結(jié)果表示當(dāng)前的遍歷過程將會(huì)繼續(xù),但是要忽略當(dāng)前目錄下的所有節(jié)點(diǎn)。
- FileVisitResult.TERMINATE:這個(gè)訪問結(jié)果表示當(dāng)前的遍歷過程將會(huì)停止。
通過訪問者去遍歷文件樹會(huì)比較方便,比如查找文件夾內(nèi)符合某個(gè)條件的文件或者某一天內(nèi)所創(chuàng)建的文件,這個(gè)類中都提供了相對(duì)應(yīng)的方法。它的實(shí)現(xiàn)也非常簡(jiǎn)單,代碼如下。
在JDK的應(yīng)用中我們提供的文件就看做是一個(gè)穩(wěn)定元素,對(duì)應(yīng)訪問者模式中的抽象元素;而Files.walkFileTree()方法中的FileVisitor 參數(shù)就可看做是角色中的訪問者。
四、訪問者模式中的偽動(dòng)態(tài)雙分派
訪問者模式中有一個(gè)重要的概念叫:偽動(dòng)態(tài)雙分派。
我們一步一步解讀它的含義,什么叫分派?根據(jù)對(duì)象的類型而對(duì)方法進(jìn)行的選擇,就是分派(Dispatch)。
發(fā)生在編譯時(shí)的分派叫靜態(tài)分派,例如重載(overload),發(fā)生在運(yùn)行時(shí)的分派叫動(dòng)態(tài)分派,例如重寫(overwrite)。
其中分派又分為單分派和多分派。
單分派:依據(jù)單個(gè)變量進(jìn)行方法的選擇就叫單分派,Java 動(dòng)態(tài)分派(重寫)只根據(jù)方法的接收者一個(gè)變量進(jìn)行分配,所以其是單分派。
多分派:依據(jù)多個(gè)變量進(jìn)行方法的選擇就叫多分派,Java 靜態(tài)分派(重載)要根據(jù)方法的接收者與參數(shù)這兩個(gè)變量進(jìn)行分配,所以其是多分派。
理解了概念我們接著看我們的案例:
@Override public void accept(VisitorAbstract visitor) { visitor.visit(this); }
我們案例中的accept方法,是由元素的運(yùn)行時(shí)類型決定的,應(yīng)該是屬于動(dòng)態(tài)單分派。
我們接著看 visitor.visit(this)又是一次動(dòng)態(tài)單分派,兩次動(dòng)態(tài)單分派實(shí)現(xiàn)了雙分派的效果,所以稱為偽動(dòng)態(tài)雙分派。
這個(gè)概念理解就好,實(shí)際應(yīng)用中知不知道這玩意都不影響。
五、總結(jié)
當(dāng)你有個(gè)類,里面的包含各種類型的元素,這個(gè)類結(jié)構(gòu)比較穩(wěn)定,不會(huì)經(jīng)常增刪不同類型的元素。而需要經(jīng)常給這些元素添加新的操作的時(shí)候,考慮使用此設(shè)計(jì)模式。
適用對(duì)象結(jié)構(gòu)比較穩(wěn)定每增加一個(gè)元素訪問者都要大變動(dòng),但加新的操作很簡(jiǎn)單。集中相關(guān)的操作、分離無關(guān)的操作。
缺點(diǎn)只有兩個(gè)字-復(fù)雜,號(hào)稱是最復(fù)雜的設(shè)計(jì)模式。
到此這篇關(guān)于Java設(shè)計(jì)模式之訪問者模式的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringCloud如何利用Feign訪問外部http請(qǐng)求
這篇文章主要介紹了SpringCloud如何利用Feign訪問外部http請(qǐng)求,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java基礎(chǔ)篇之對(duì)象數(shù)組練習(xí)
對(duì)象數(shù)組就是數(shù)組里的每個(gè)元素都是類的對(duì)象,賦值時(shí)先定義對(duì)象,然后將對(duì)象直接賦給數(shù)組就行了,這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)篇之對(duì)象數(shù)組練習(xí)的相關(guān)資料,需要的朋友可以參考下2024-03-03詳解SpringBoot如何刪除引用jar包中的無用bean
為了趕速度和直接將之前多模塊的maven項(xiàng)目中的部分模塊,直接以jar包的形式引入到新項(xiàng)目中了,雖然省去了不少開發(fā)時(shí)間,導(dǎo)致項(xiàng)目臃腫,啟動(dòng)很慢。本文將用@ComponentScan注解去實(shí)現(xiàn)讓項(xiàng)目只加載自己需要的bean,需要的可以參考一下2022-06-06Netty框架實(shí)現(xiàn)TCP/IP通信的完美過程
這篇文章主要介紹了Netty框架實(shí)現(xiàn)TCP/IP通信,這里使用的是Springboot+Netty框架,使用maven搭建項(xiàng)目,需要的朋友可以參考下2021-07-07SpringBoot如何接收前端傳來的json數(shù)據(jù)
這篇文章主要介紹了SpringBoot如何接收前端傳來的json數(shù)據(jù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Springboot集成Quartz實(shí)現(xiàn)定時(shí)任務(wù)代碼實(shí)例
這篇文章主要介紹了Springboot集成Quartz實(shí)現(xiàn)定時(shí)任務(wù)代碼實(shí)例,任務(wù)是有可能并發(fā)執(zhí)行的,若Scheduler直接使用Job,就會(huì)存在對(duì)同一個(gè)Job實(shí)例并發(fā)訪問的問題,而JobDetail?&?Job方式,Scheduler都會(huì)根據(jù)JobDetail創(chuàng)建一個(gè)新的Job實(shí)例,這樣就可以規(guī)避并發(fā)訪問問題2023-09-09SpringBoot整合數(shù)據(jù)庫訪問層的實(shí)戰(zhàn)
本文主要介紹了SpringBoot整合數(shù)據(jù)庫訪問層的實(shí)戰(zhàn),主要包含JdbcTemplate和mybatis框架的整合應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2022-03-03idea2020中復(fù)制一個(gè)微服務(wù)實(shí)例的方法
這篇文章主要介紹了idea2020中復(fù)制一個(gè)微服務(wù)實(shí)例的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09springmvc 傳遞和接收數(shù)組參數(shù)的實(shí)例
下面小編就為大家分享一篇springmvc 傳遞和接收數(shù)組參數(shù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03