介紹:
訪問者模式屬于行為型模式。表示一個(gè)作用于某對象結(jié)構(gòu)中的各元素的操作。它使你可以在不改變各元素的類前提下定義作用于這些元素的新操作。
它的基本思想是:將數(shù)據(jù)操作與數(shù)據(jù)結(jié)構(gòu)分離,將一些穩(wěn)定的對象的類擁有一個(gè)accept方法用來接收訪問者訪問。
類圖:

? Visitor(抽象訪問者):定義每一個(gè)元素(Element)訪問的行為。
? ConcreteVisitor(具體訪問者):需要對每一個(gè)元素訪問時(shí)所產(chǎn)生具體行為。
? Element(抽象元素):定義一個(gè)接受訪問者的方法accept(Visitor v)。指每一個(gè)元素都可以被訪問者訪問。
? ElementA、ElementB(具體元素):給接受訪問者方法accept(Visitor v)的具體實(shí)現(xiàn)。
? ObjectStructure(對象結(jié)構(gòu)):管理元素集合
用法:
? 對象結(jié)構(gòu)比較穩(wěn)定,但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作。
? 需要對一個(gè)對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免這些操作“污染”這些對象的類,也不希望再增加新操作時(shí)修改這些類。
例子:
年終時(shí),公司高層會根據(jù)每個(gè)員工這一年做出的功勞發(fā)年終獎(jiǎng),對基層員工和對管理層員工發(fā)的獎(jiǎng)金也是不一樣。我們模擬一下這個(gè)例子:
設(shè)計(jì)思路:為了簡單模擬這個(gè)過程,我們把員工分為工程師和經(jīng)理,公司高層分為CTO和CEO。CTO只關(guān)注代碼行數(shù)和代碼質(zhì)量,CEO只關(guān)注業(yè)績。
1、第一版代碼:
1.1、員工基類
public abstract class Staff {
public String name;
public int kpi;
public Staff(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public abstract void evaluate(String s); //高層調(diào)用評價(jià)
}
我們用面向?qū)ο蟮闹R,抽象出公共部分。evaluate(String s)方法會傳入ceo或cto。
1.2、經(jīng)理類
public class Manager extends Staff {
private int products; //產(chǎn)品數(shù)量
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
public int getProducts() {
return products;
}
@Override
public void evaluate(String s){
if (s.equals("ceo")){
System.out.println("經(jīng)理:" + name + ",新產(chǎn)品數(shù)量:" + getProducts());
} else if (s.equals("cto")){
System.out.println("經(jīng)理:" + name + ",KPI:" + kpi + ",新產(chǎn)品數(shù)量:" + getProducts());
}
}
}
經(jīng)理類新增屬性產(chǎn)品數(shù)量。被評價(jià)時(shí)會根據(jù)產(chǎn)品數(shù)量和kpi去考核。
1.3、工程師類
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
public int getCodeLines(){
return new Random().nextInt(10 * 10000);
}
@Override
public void evaluate(String s){
if (s.equals("ceo")){
System.out.println("工程師:" + name + ",KPI:" + kpi);
} else if (s.equals("cto")){
System.out.println("工程師:" + name + ",代碼數(shù)量:" + getCodeLines());
}
}
}
程序員的屬性是代碼行數(shù),被評價(jià)時(shí)通常以代碼行數(shù)作為依據(jù)。
1.4、報(bào)表類
public class BusinessReport {
List<Staff> mStaffs = new ArrayList<>();
public BusinessReport(){
mStaffs.add(new Manager("王經(jīng)理"));
mStaffs.add(new Manager("陳經(jīng)理"));
mStaffs.add(new Engineer("Android開發(fā)者"));
mStaffs.add(new Engineer("iOS開發(fā)者"));
mStaffs.add(new Engineer("php開發(fā)者"));
}
public void showReport(String s){
for (Staff staff : mStaffs){
staff.evaluate(s);
}
}
}
1.5、實(shí)現(xiàn)與測試
public class Test {
public static void main(String[] args) {
//構(gòu)建報(bào)表
BusinessReport businessReport = new BusinessReport();
System.out.println("====給CEO看的報(bào)表====");
businessReport.showReport("ceo");
System.out.println("====給CTO看的報(bào)表====");
businessReport.showReport("cto");
}
}
====給CEO看的報(bào)表====
經(jīng)理:王經(jīng)理,新產(chǎn)品數(shù)量:2
經(jīng)理:陳經(jīng)理,新產(chǎn)品數(shù)量:3
工程師:Android開發(fā)者,KPI:5
工程師:iOS開發(fā)者,KPI:7
工程師:php開發(fā)者,KPI:4
====給CTO看的報(bào)表====
經(jīng)理:王經(jīng)理,KPI:4,新產(chǎn)品數(shù)量:2
經(jīng)理:陳經(jīng)理,KPI:1,新產(chǎn)品數(shù)量:3
工程師:Android開發(fā)者,代碼數(shù)量:23990
工程師:iOS開發(fā)者,代碼數(shù)量:69770
工程師:php開發(fā)者,代碼數(shù)量:770
我們簡單分析一下這個(gè)做法:這樣做比較簡單也實(shí)現(xiàn)了功能,但沒用到設(shè)計(jì)模式。如果評分的高管加多一個(gè)副總,那我們需要在Engineer類和Manager類的分支那里加多一個(gè)判斷,違反了開閉原則。
2、訪問者模式實(shí)現(xiàn):
我們運(yùn)用訪問者模式的套路實(shí)現(xiàn)一下,我們根據(jù)第一個(gè)例子改版一下:
數(shù)據(jù)結(jié)構(gòu)(元素):Manager類和Engineer類,是一個(gè)比較穩(wěn)定的結(jié)構(gòu),并且各自有自己的屬性。
數(shù)據(jù)操作(訪問者):將evaluate()評價(jià)操作分離出來。
2.1、抽象元素類
public abstract class Staff {
public String name;
public int kpi;
public Staff(String name){
this.name = name;
this.kpi = new Random().nextInt(10);
}
public abstract void accept(Vistor vistor);
}
定義一個(gè)接受訪問者的方法accept(Visitor v)方法。將1.1evaluate()方法干掉。此類作為抽象元素類。
2.2、具體元素類
public class Manager extends Staff {
private int products; //產(chǎn)品數(shù)量
public Manager(String name) {
super(name);
products = new Random().nextInt(10);
}
@Override
public void accept(Vistor vistor) {
vistor.vist(this);
}
public int getProducts() {
return products;
}
}
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
@Override
public void accept(Vistor vistor) {
vistor.vist(this);
}
public int getCodeLines(){
return new Random().nextInt(10 * 10000);
}
}
根據(jù)上文1.2、1.3的兩個(gè)類,我們做出了一些改變,實(shí)現(xiàn)accept()方法,將自身傳給Visitor去進(jìn)行數(shù)據(jù)操作。
2.3、抽象訪問者類
public interface Vistor {
void vist(Engineer engineer);
void vist(Manager manager);
}
定義每一個(gè)元素(Element)訪問的行為。
2.4、具體訪問者類
public class CEOVistor implements Vistor {
@Override
public void vist(Engineer engineer) {
System.out.println("工程師:" + engineer.name + ",KPI:" + engineer.kpi);
}
@Override
public void vist(Manager manager) {
System.out.println("經(jīng)理:" + manager.name + ",KPI:" + manager.kpi + ",新產(chǎn)品數(shù)量:" + manager.getProducts());
}
}
public class CTOVistor implements Vistor {
@Override
public void vist(Engineer engineer) {
System.out.println("工程師:" + engineer.name + ",代碼數(shù)量:" + engineer.getCodeLines());
}
@Override
public void vist(Manager manager) {
System.out.println("經(jīng)理:" + manager.name + ",產(chǎn)品數(shù)量:" + manager.getProducts());
}
}
每一個(gè)訪問者對所有元素的訪問邏輯的具體行為。相當(dāng)于我們將上文1.2、1.3的evaluate()方法抽離成類了。
2.5、報(bào)表類ObjectStructure
public class BusinessReport {
List<Staff> mStaffs = new ArrayList<>();
public BusinessReport(){
mStaffs.add(new Manager("王經(jīng)理"));
mStaffs.add(new Manager("陳經(jīng)理"));
mStaffs.add(new Engineer("Android開發(fā)者"));
mStaffs.add(new Engineer("iOS開發(fā)者"));
mStaffs.add(new Engineer("php開發(fā)者"));
}
/**
*
* @param vistor
*/
public void showReport(Vistor vistor){
for (Staff staff : mStaffs){
staff.accept(vistor);
}
}
}
修改了showReport方法,將訪問者和元素做一個(gè)關(guān)聯(lián)。
2.6、測試與實(shí)現(xiàn)
public class Test {
public static void main(String[] args) {
//構(gòu)建報(bào)表
BusinessReport businessReport = new BusinessReport();
System.out.println("====給CEO看的報(bào)表====");
businessReport.showReport(new CEOVistor());
System.out.println("====給CTO看的報(bào)表====");
businessReport.showReport(new CTOVistor());
}
}
實(shí)現(xiàn)效果跟上文1.5中一樣,下面我們分析一下:
整個(gè)過程把數(shù)據(jù)操作部分抽象出Visitor類,數(shù)據(jù)結(jié)構(gòu)部分作為元素類定義一個(gè)accept(this)的方法讓訪問者訪問。
如果評分的高管增加一個(gè)副總,可以使不改變各元素的類前提下定義作用于這些元素的新操作,也就是副總的評分我不用在員工類內(nèi)部的evaluate(String s)方法增加多一個(gè)副總的分支,但需要新建一個(gè)副總的Visitor去實(shí)現(xiàn)。
總結(jié):
此模式使用頻率不高,知道怎么用就好。這個(gè)模式最大的特點(diǎn)就是數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù)操作的分離,我們追求數(shù)據(jù)結(jié)構(gòu)穩(wěn)定性的同時(shí)將數(shù)據(jù)操作抽象出去,Visitor的改動(dòng)成本和新增元素的成本也是挺大的,還是不要濫用好。
感謝您的閱讀~
轉(zhuǎn)載請注明出處喔:http://m.itdecent.cn/p/bf4bca9e82e9
推薦閱讀
基礎(chǔ)篇:
設(shè)計(jì)模式前篇之——UML類圖必會知識點(diǎn)
設(shè)計(jì)模式前篇之——一起過一下面向?qū)ο蟮母拍?/a>
創(chuàng)建型模式:
簡易理解設(shè)計(jì)模式之:簡單工廠模式——來試試接入支付功能
簡易理解設(shè)計(jì)模式之:工廠方法模式——數(shù)據(jù)存儲例子
簡易理解設(shè)計(jì)模式之:抽象工廠模式——更換數(shù)據(jù)庫例子
簡易理解設(shè)計(jì)模式之:建造者模式——學(xué)習(xí)使用“鏈?zhǔn)秸{(diào)用”
簡易理解設(shè)計(jì)模式之:原型模式——深、淺拷貝的概念
簡易理解設(shè)計(jì)模式之:單例模式——單例模式的幾種常用寫法
結(jié)構(gòu)型模式:
簡易理解設(shè)計(jì)模式之:適配器模式——Android列表視圖控件設(shè)計(jì)方式
簡易理解設(shè)計(jì)模式之:橋接模式——穿衣服經(jīng)典案例2
簡易理解設(shè)計(jì)模式之:組合模式——實(shí)現(xiàn)View中的樹狀結(jié)構(gòu)
簡易理解設(shè)計(jì)模式之:裝飾模式——穿衣服經(jīng)典案例
簡易理解設(shè)計(jì)模式之:外觀模式——第三方SDK的幫助類
簡易理解設(shè)計(jì)模式之:享元模式——五子棋游戲例子
簡易理解設(shè)計(jì)模式之:代理模式——iOS視圖控件設(shè)計(jì)方式
行為型模式:
簡易理解設(shè)計(jì)模式之:策略模式——優(yōu)化一下支付功能
簡易理解設(shè)計(jì)模式之:模板方法模式——Android中的BaseActivity基類
簡易理解設(shè)計(jì)模式之:觀察者模式——監(jiān)聽與回調(diào)
簡易理解設(shè)計(jì)模式之:狀態(tài)模式——優(yōu)化登錄操作
簡易理解設(shè)計(jì)模式之:備忘錄模式——Word文檔的工作原理
簡易理解設(shè)計(jì)模式之:迭代器模式——遍歷對象的好幫手
簡易理解設(shè)計(jì)模式之:命令模式——實(shí)現(xiàn)命令的參數(shù)化配置
簡易理解設(shè)計(jì)模式之:責(zé)任鏈模式——OA中請假流程示例
簡易理解設(shè)計(jì)模式之:中介者模式——多人聊天室例子
簡易理解設(shè)計(jì)模式之:解釋器模式——語言和文法
簡易理解設(shè)計(jì)模式之:訪問者模式——員工考核例子