「補(bǔ)課」進(jìn)行時(shí):設(shè)計(jì)模式(18)——訪問(wèn)者模式

1. 前文匯總
「補(bǔ)課」進(jìn)行時(shí):設(shè)計(jì)模式系列
2. 引言
訪問(wèn)者模式也可以說(shuō)是所有設(shè)計(jì)模式中最難的一種設(shè)計(jì)模式了,當(dāng)然我們平常也很少會(huì)用到它。設(shè)計(jì)模式的作者是這么評(píng)價(jià)訪問(wèn)者模式的:大多情況下,你并不需要使用訪問(wèn)者模式,但是一旦需要使用它時(shí),那就真的需要使用了。
3. 一個(gè)簡(jiǎn)單的示例
又快到年底, CEO 和 CTO 開始評(píng)定員工一年的工作績(jī)效,員工分為工程師和經(jīng)理, CTO 關(guān)注工程師的代碼量、經(jīng)理的新產(chǎn)品數(shù)量;CEO 關(guān)注的是工程師的KPI和經(jīng)理的KPI以及新產(chǎn)品數(shù)量。
由于 CEO 和 CTO 對(duì)于不同員工的關(guān)注點(diǎn)是不一樣的,這就需要對(duì)不同員工類型進(jìn)行不同的處理。訪問(wèn)者模式此時(shí)可以派上用場(chǎng)了。
首先定義一個(gè)員工基類 Staff :
public?abstract?class?Staff?{
????public?String?name;
????//?員工KPI
????public?int?kpi;
????public?Staff(String?name)?{
????????this.name?=?name;
????????kpi?=?new?Random().nextInt(10);
????}
????//?核心方法,接受Visitor的訪問(wèn)
????abstract?void?accept(Visitor?visitor);
}
Staff 類定義了員工基本信息及一個(gè) accept() 方法, accept() 方法表示接受訪問(wèn)者的訪問(wèn),由子類具體實(shí)現(xiàn)。
而 Visitor 是個(gè)接口,傳入不同的實(shí)現(xiàn)類,可訪問(wèn)不同的數(shù)據(jù)。
下面是工程師和經(jīng)理的具體實(shí)現(xiàn)類:
public?class?Engineer?extends?Staff?{
????public?Engineer(String?name)?{
????????super(name);
????}
????@Override
????void?accept(Visitor?visitor)?{
????????visitor.visit(this);
????}
????//?工程師一年的代碼數(shù)量
????public?int?getCodeLines()?{
????????return?new?Random().nextInt(10?*?10000);
????}
}
public?class?Manager?extends?Staff?{
????public?Manager(String?name)?{
????????super(name);
????}
????@Override
????void?accept(Visitor?visitor)?{
????????visitor.visit(this);
????}
????//?一年做的產(chǎn)品數(shù)量
????public?int?getProducts()?{
????????return?new?Random().nextInt(10);
????}
}
工程師是代碼數(shù)量,經(jīng)理是產(chǎn)品數(shù)量,他們的職責(zé)不一樣,也就是因?yàn)椴町愋裕攀沟迷L問(wèn)模式能夠發(fā)揮它的作用。
下面是 Visitor 接口的定義:
public?interface?Visitor?{
????//?訪問(wèn)工程師類型
????void?visit(Engineer?engineer);
????//?訪問(wèn)經(jīng)理類型
????void?visit(Manager?manager);
}
Visitor 聲明了兩個(gè) visit 方法,分別是對(duì)工程師和經(jīng)理對(duì)訪問(wèn)函數(shù)。
接下來(lái)定義兩個(gè)具體的訪問(wèn)者:CEO 和 CTO 。
public?class?CEOVisitor?implements?Visitor?{
????@Override
????public?void?visit(Engineer?engineer)?{
????????System.out.println("工程師:?"?+?engineer.name?+?",?KPI:?"?+?engineer.kpi);
????}
????@Override
????public?void?visit(Manager?manager)?{
????????System.out.println("經(jīng)理:?"?+?manager.name?+?",?KPI:?"?+?manager.kpi?+?",?新產(chǎn)品數(shù)量:?"?+?manager.getProducts());
????}
}
public?class?CTOVisitor?implements?Visitor?{
????@Override
????public?void?visit(Engineer?engineer)?{
????????System.out.println("工程師:?"?+?engineer.name?+?",?代碼行數(shù):?"?+?engineer.getCodeLines());
????}
????@Override
????public?void?visit(Manager?manager)?{
????????System.out.println("經(jīng)理:?"?+?manager.name?+?",?產(chǎn)品數(shù)量:?"?+?manager.getProducts());
????}
}
接著是一個(gè)報(bào)表類,公司的 CEO 和 CTO 通過(guò)這個(gè)報(bào)表查看所有員工的業(yè)績(jī):
public?class?BusinessReport?{
????private?List?mStaffs?=?new?LinkedList<>();
????public?BusinessReport()?{
????????mStaffs.add(new?Manager("經(jīng)理-A"));
????????mStaffs.add(new?Engineer("工程師-A"));
????????mStaffs.add(new?Engineer("工程師-B"));
????????mStaffs.add(new?Manager("經(jīng)理-B"));
????????mStaffs.add(new?Engineer("工程師-C"));
????}
????/**
?????*?為訪問(wèn)者展示報(bào)表
?????*?@param?visitor?公司高層,如?CEO、CTO
?????*/
????public?void?showReport(Visitor?visitor)?{
????????for?(Staff?staff?:?mStaffs)?{
????????????staff.accept(visitor);
????????}
????}
}
最后是一個(gè)場(chǎng)景類:
public?class?Client?{
????public?static?void?main(String[]?args)?{
????????//?構(gòu)建報(bào)表
????????BusinessReport?report?=?new?BusinessReport();
????????System.out.println("===========?CEO看報(bào)表?===========");
????????report.showReport(new?CEOVisitor());
????????System.out.println("===========?CTO看報(bào)表?===========");
????????report.showReport(new?CTOVisitor());
????}
}
執(zhí)行結(jié)果如下:
===========?CEO看報(bào)表?===========
經(jīng)理:?經(jīng)理-A,?KPI:?7,?新產(chǎn)品數(shù)量:?8
工程師:?工程師-A,?KPI:?6
工程師:?工程師-B,?KPI:?3
經(jīng)理:?經(jīng)理-B,?KPI:?4,?新產(chǎn)品數(shù)量:?4
工程師:?工程師-C,?KPI:?2
===========?CTO看報(bào)表?===========
經(jīng)理:?經(jīng)理-A,?產(chǎn)品數(shù)量:?6
工程師:?工程師-A,?代碼行數(shù):?61280
工程師:?工程師-B,?代碼行數(shù):?10353
經(jīng)理:?經(jīng)理-B,?產(chǎn)品數(shù)量:?5
工程師:?工程師-C,?代碼行數(shù):?65827
4. 訪問(wèn)者模式
4.1 定義
訪問(wèn)者模式(Visitor Pattern) 是一個(gè)相對(duì)簡(jiǎn)單的模式, 其定義如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作, 它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。)
4.2 通用類圖

Visitor 抽象訪問(wèn)者:抽象類或者接口,聲明訪問(wèn)者可以訪問(wèn)哪些元素。 ConcreteVisitor 具體訪問(wèn)者:它影響訪問(wèn)者訪問(wèn)到一個(gè)類后該怎么干, 要做什么事情。 Element 抽象元素:接口或者抽象類,聲明接受哪一類訪問(wèn)者訪問(wèn)。 ConcreteElement 具體元素:實(shí)現(xiàn)方法。 ObjectStruture 結(jié)構(gòu)對(duì)象:元素產(chǎn)生者,一般容納在多個(gè)不同類、不同接口的容器。
4.3 通用代碼
抽象元素:
public?abstract?class?Element?{
????//?定義業(yè)務(wù)邏輯
????abstract?void?doSomething();
????//?定義允許訪問(wèn)角色
????abstract?void?accept(IVisitor?visitor);
}
具體元素:
public?class?ConcreteElement1?extends?Element{
????@Override
????void?doSomething()?{
????}
????@Override
????void?accept(IVisitor?visitor)?{
????????visitor.visit(this);
????}
}
public?class?ConcreteElement2?extends?Element{
????@Override
????void?doSomething()?{
????}
????@Override
????void?accept(IVisitor?visitor)?{
????????visitor.visit(this);
????}
}
抽象訪問(wèn)者:
public?interface?IVisitor?{
????void?visit(ConcreteElement1?ele1);
????void?visit(ConcreteElement2?ele2);
}
具體訪問(wèn)者:
public?class?Visitor?implements?IVisitor{
????@Override
????public?void?visit(ConcreteElement1?ele1)?{
????????ele1.doSomething();
????}
????@Override
????public?void?visit(ConcreteElement2?ele2)?{
????????ele2.doSomething();
????}
}
結(jié)構(gòu)對(duì)象:
public?class?ObjectStruture?{
????public?static?Element?createElement()?{
????????Random?random?=?new?Random();
????????if?(random.nextInt(100)?>?50)?{
????????????return?new?ConcreteElement1();
????????}?else?{
????????????return?new?ConcreteElement2();
????????}
????}
}
場(chǎng)景類:
public?class?Client?{
????public?static?void?main(String[]?args)?{
????????for?(int?i?=?0;?i?10;?i++)?{
????????????Element?e1?=?ObjectStruture.createElement();
????????????e1.accept(new?Visitor());
????????}
????}
}
4.4 優(yōu)點(diǎn)
各角色職責(zé)分離,符合單一職責(zé)原則。
通過(guò)UML類圖和上面的示例可以看出來(lái),Visitor、ConcreteVisitor、Element 、ObjectStructure,職責(zé)單一,各司其責(zé)。
具有優(yōu)秀的擴(kuò)展性。
如果需要增加新的訪問(wèn)者,增加實(shí)現(xiàn)類 ConcreteVisitor 就可以快速擴(kuò)展。
使得數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦,使得操作集合可以獨(dú)立變化。
員工屬性(數(shù)據(jù)結(jié)構(gòu))和CEO、CTO訪問(wèn)者(數(shù)據(jù)操作)的解耦。
靈活性。
4.5 缺點(diǎn)
具體元素對(duì)訪問(wèn)者公布細(xì)節(jié),違反了迪米特原則。
CEO、CTO需要調(diào)用具體員工的方法。
具體元素變更時(shí)導(dǎo)致修改成本大。
變更員工屬性時(shí),多個(gè)訪問(wèn)者都要修改。
違反了依賴倒置原則,為了達(dá)到「區(qū)別對(duì)待」而依賴了具體類,沒(méi)有用來(lái)抽象。
訪問(wèn)者 visit 方法中,依賴了具體員工的具體方法。

