Java設(shè)計模式之訪問者模式
大多數(shù)情況下你不需要訪問者模式,但當一旦需要訪問者模式時,那就是真的需要它了,這是設(shè)計模式創(chuàng)始人的原話??梢钥闯鰬?yīng)用場景比較少,但需要它的時候是不可或缺的,這篇文章就開始學(xué)習(xí)最后一個設(shè)計模式——訪問者模式。
一、概念理解
訪問者模式概念:封裝作用于某對象結(jié)構(gòu)中的各元素的操作,它使你可以在不改變各元素的類的前提下定義作用于這些元素的新操作。
通俗的解釋就是,系統(tǒng)中有一些固定結(jié)構(gòu)的對象(元素),在其內(nèi)部提供一個accept()方法用來接受訪問者對象的訪問,不同的訪問者對同一元素的訪問內(nèi)容不同,所以使得相同的元素可以產(chǎn)生不同的元素結(jié)果。
比如在一個人事管理系統(tǒng)中,有多個工種的員工和多個老板,不同的老板對同一個員工的關(guān)注點是不同的,CTO可能關(guān)注的就是技術(shù),CEO可能更注重績效。
員工就是一個穩(wěn)定的元素,老板就是變化的,對應(yīng)概念就是:封裝員工的一些操作,可以在不改變員工類的前提下,增加新的老板訪問同一個員工。
在訪問者模式中包含五個角色,抽象元素、具體元素、抽象訪問者、具體訪問者、結(jié)構(gòu)元素。
抽象元素:定義一個接受訪問的方法accept,參數(shù)為訪問者對象。
具體元素:提供接受訪問者訪問的具體實現(xiàn)調(diào)用訪問者的訪問visit,并定義額外的數(shù)據(jù)操作方法。
抽象訪問者:這個角色主要是定義對具體元素的訪問方法visit,理論上來說方法數(shù)等于元素(固定類型的對象,也就是被訪問者)個數(shù)。
具體訪問者:實現(xiàn)對具體元素的訪問visit方法,參數(shù)就是具體元素。
結(jié)構(gòu)對象:創(chuàng)建一個數(shù)組用來維護元素,并提供一個方法訪問所有的元素。
二、案例實現(xiàn)
在一個公司有干活的工程師和管理者,也有抓技術(shù)的CTO和管績效的CEO,CTO和CEO都會訪問管理員和工程師,當公司來了新的老板,只需要增加訪問者即可。
工程師和管理者就是元素、公司就是結(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() + " 今年共完成項目:" + manager.getProductNum() + "個");
}
}具體訪問者-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() + " 今年共完成項目:" + manager.getProductNum() + "個");
}
}結(jié)構(gòu)體:
/**
* 結(jié)構(gòu)對象
* @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) {
//元素對象
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)體對象
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è)計模式,方法間的調(diào)用錯綜復(fù)雜,日常開發(fā)的使用頻率很低,很多程序員寧可代碼寫的麻煩一點也不用這種設(shè)計模式,但是作為學(xué)習(xí)者就要學(xué)習(xí)各種設(shè)計模式了。
三、訪問者模式在JDk中的應(yīng)用
JDK的NIO中的 FileVisitor 接口采用的就是訪問者模式。
在早期的 Java 版本中,如果要對指定目錄下的文件進行遍歷,必須用遞歸的方式來實現(xiàn),這種方法復(fù)雜且靈活性不高。
Java 7 版本后,F(xiàn)iles 類提供了 walkFileTree() 方法,該方法可以很容易的對目錄下的所有文件進行遍歷,需要 Path、FileVisitor 兩個參數(shù)。其中,Path 是要遍歷文件的路徑,F(xiàn)ileVisitor 則可以看成一個文件訪問器,源碼如下。
FileVisitor 主要提供了 4 個方法,且返回結(jié)果的都是 FileVisitResult 對象值,用于決定當前操作完成后接下來該如何處理。FileVisitResult 是一個枚舉類,代表返回之后的一些后續(xù)操作,源碼如下。
FileVisitResult 主要包含 4 個常見的操作。
- FileVisitResult.CONTINUE:這個訪問結(jié)果表示當前的遍歷過程將會繼續(xù)。
- FileVisitResult.SKIP_SIBLINGS:這個訪問結(jié)果表示當前的遍歷過程將會繼續(xù),但是要忽略當前文件/目錄的兄弟節(jié)點。
- FileVisitResult.SKIP_SUBTREE:這個訪問結(jié)果表示當前的遍歷過程將會繼續(xù),但是要忽略當前目錄下的所有節(jié)點。
- FileVisitResult.TERMINATE:這個訪問結(jié)果表示當前的遍歷過程將會停止。
通過訪問者去遍歷文件樹會比較方便,比如查找文件夾內(nèi)符合某個條件的文件或者某一天內(nèi)所創(chuàng)建的文件,這個類中都提供了相對應(yīng)的方法。它的實現(xiàn)也非常簡單,代碼如下。
在JDK的應(yīng)用中我們提供的文件就看做是一個穩(wěn)定元素,對應(yīng)訪問者模式中的抽象元素;而Files.walkFileTree()方法中的FileVisitor 參數(shù)就可看做是角色中的訪問者。
四、訪問者模式中的偽動態(tài)雙分派
訪問者模式中有一個重要的概念叫:偽動態(tài)雙分派。
我們一步一步解讀它的含義,什么叫分派?根據(jù)對象的類型而對方法進行的選擇,就是分派(Dispatch)。
發(fā)生在編譯時的分派叫靜態(tài)分派,例如重載(overload),發(fā)生在運行時的分派叫動態(tài)分派,例如重寫(overwrite)。
其中分派又分為單分派和多分派。
單分派:依據(jù)單個變量進行方法的選擇就叫單分派,Java 動態(tài)分派(重寫)只根據(jù)方法的接收者一個變量進行分配,所以其是單分派。
多分派:依據(jù)多個變量進行方法的選擇就叫多分派,Java 靜態(tài)分派(重載)要根據(jù)方法的接收者與參數(shù)這兩個變量進行分配,所以其是多分派。
理解了概念我們接著看我們的案例:
@Override
public void accept(VisitorAbstract visitor) {
visitor.visit(this);
}我們案例中的accept方法,是由元素的運行時類型決定的,應(yīng)該是屬于動態(tài)單分派。
我們接著看 visitor.visit(this)又是一次動態(tài)單分派,兩次動態(tài)單分派實現(xiàn)了雙分派的效果,所以稱為偽動態(tài)雙分派。
這個概念理解就好,實際應(yīng)用中知不知道這玩意都不影響。
五、總結(jié)
當你有個類,里面的包含各種類型的元素,這個類結(jié)構(gòu)比較穩(wěn)定,不會經(jīng)常增刪不同類型的元素。而需要經(jīng)常給這些元素添加新的操作的時候,考慮使用此設(shè)計模式。
適用對象結(jié)構(gòu)比較穩(wěn)定每增加一個元素訪問者都要大變動,但加新的操作很簡單。集中相關(guān)的操作、分離無關(guān)的操作。
缺點只有兩個字-復(fù)雜,號稱是最復(fù)雜的設(shè)計模式。
到此這篇關(guān)于Java設(shè)計模式之訪問者模式的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot集成Tess4J實現(xiàn)OCR的示例代碼
Tess4J是一個基于Tesseract OCR引擎的Java接口,可以用來識別圖像中的文本,說白了,就是封裝了它的API,讓Java可以直接調(diào)用,本文給大家介紹了SpringBoot集成Tess4J實現(xiàn)OCR的示例,需要的朋友可以參考下2024-12-12
SpringBoot優(yōu)化接口響應(yīng)時間的九個技巧
在實際開發(fā)中,提升接口響應(yīng)速度是一件挺重要的事,特別是在面臨大量用戶請求的時候,本文為大家整理了9個SpringBoot優(yōu)化接口響應(yīng)時間的技巧,希望對大家有所幫助2024-01-01




