定義:
封裝某些作用于某種數(shù)據(jù)結(jié)構(gòu)中各元素的操作泽艘,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作斥黑。
例子:
生成一個(gè)報(bào)表(員工考核)給不同領(lǐng)導(dǎo)看萝毛,員工的工作和表現(xiàn)數(shù)據(jù)已經(jīng)固定了(被訪問(wèn)者),這個(gè)時(shí)候?qū)τ诓煌I(lǐng)導(dǎo)(訪問(wèn)者)來(lái)說(shuō)言蛇,
他們考核員工的標(biāo)準(zhǔn)不同亮曹,ceo看重員工kpi,cto看重代碼和產(chǎn)品數(shù)岔留;而員工(被訪問(wèn)者)的類型又不同庵佣,每個(gè)員工都有kpi,但是程序員還有代碼量婿斥,經(jīng)理有產(chǎn)品數(shù)劝篷。
最終達(dá)到的結(jié)果是報(bào)表,數(shù)據(jù)來(lái)自被訪問(wèn)者民宿,報(bào)表就是處理訪問(wèn)者和被訪問(wèn)者的關(guān)系或者邏輯娇妓。即數(shù)據(jù)和數(shù)據(jù)邏輯分離;
示例代碼
class A {
public void method1(){
System.out.println("我是A");
}
public void method2(B b){
b.showA(this);
}
}
class B {
public void showA(A a){
a.method1();
}
}
public class Test {
public static void main(String[] args){
A a = new A();
a.method1();
a.method2(new B());
}
}
在例子中活鹰,對(duì)于類A來(lái)說(shuō)哈恰,類B就是一個(gè)訪問(wèn)者。但是這個(gè)例子并不是訪問(wèn)者模式的全部华望,雖然直觀蕊蝗,但是它的可擴(kuò)展性比較差
角色
抽象訪問(wèn)者(IVisitor ):
抽象類或者接口,聲明訪問(wèn)者可以訪問(wèn)哪些元素赖舟,具體到程序中就是visit方法中的參數(shù)定義哪些對(duì)象是可以被訪問(wèn)的蓬戚。訪問(wèn)者(Visitor ):
實(shí)現(xiàn)抽象訪問(wèn)者所聲明的方法,它影響到訪問(wèn)者訪問(wèn)到一個(gè)類后該干什么宾抓,要做什么事情子漩。抽象元素類(Element ):
接口或者抽象類,聲明接受哪一類訪問(wèn)者訪問(wèn)石洗,程序上是通過(guò)accept方法中的參數(shù)來(lái)定義的幢泼。抽象元素一般有兩類方法,一部分是本身的業(yè)務(wù)邏輯讲衫,另外就是允許接收哪類訪問(wèn)者來(lái)訪問(wèn)缕棵。元素類( ConcreteElement):
實(shí)現(xiàn)抽象元素類所聲明的accept方法,通常都是visitor.visit(this)涉兽,基本上已經(jīng)形成一種定式了招驴。結(jié)構(gòu)對(duì)象(ObjectStruture ):
一個(gè)元素的容器,一般包含一個(gè)容納多個(gè)不同類枷畏、不同接口的容器别厘,如List、Set拥诡、Map等触趴,在項(xiàng)目中一般很少抽象出這個(gè)角色氮发。
實(shí)現(xiàn)代碼
IVisitor
interface IVisitor {
public void visit(ConcreteElement1 el1);
public void visit(ConcreteElement2 el2);
}
Visitor
class Visitor implements IVisitor {
public void visit(ConcreteElement1 el1) {
el1.doSomething();
}
public void visit(ConcreteElement2 el2) {
el2.doSomething();
}
}
Element
abstract class Element {
public abstract void accept(IVisitor visitor);
public abstract void doSomething();
}
ConcreteElement
class ConcreteElement1 extends Element {
public void doSomething(){
System.out.println("這是元素1");
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
class ConcreteElement2 extends Element {
public void doSomething(){
System.out.println("這是元素2");
}
public void accept(IVisitor visitor) {
visitor.visit(this);
}
}
ObjectStruture
class ObjectStruture {
public static List<Element> getList(){
List<Element> list = new ArrayList<Element>();
Random ran = new Random();
for(int i=0; i<10; i++){
int a = ran.nextInt(100);
if(a>50){
list.add(new ConcreteElement1());
}else{
list.add(new ConcreteElement2());
}
}
return list;
}
}
Client
public class Client {
public static void main(String[] args){
List<Element> list = ObjectStruture.getList();
for(Element e: list){
e.accept(new Visitor());
}
}
}
優(yōu)點(diǎn)
符合單一職責(zé)原則:凡是適用訪問(wèn)者模式的場(chǎng)景中,元素類中需要封裝在訪問(wèn)者中的操作必定是與元素類本身關(guān)系不大且是易變的操作冗懦,使用訪問(wèn)者模式一方面符合單一職責(zé)原則爽冕,另一方面,因?yàn)楸环庋b的操作通常來(lái)說(shuō)都是易變的披蕉,所以當(dāng)發(fā)生變化時(shí)扇售,就可以在不改變?cè)仡惐旧淼那疤嵯拢瑢?shí)現(xiàn)對(duì)變化部分的擴(kuò)展嚣艇。
擴(kuò)展性良好:元素類可以通過(guò)接受不同的訪問(wèn)者來(lái)實(shí)現(xiàn)對(duì)不同操作的擴(kuò)展。
適用場(chǎng)景
- 假如一個(gè)對(duì)象中存在著一些與本對(duì)象不相干(或者關(guān)系較弱)的操作华弓,為了避免這些操作污染這個(gè)對(duì)象食零,則可以使用訪問(wèn)者模式來(lái)把這些操作封裝到訪問(wèn)者中去。
- 假如一組對(duì)象中寂屏,存在著相似的操作贰谣,為了避免出現(xiàn)大量重復(fù)的代碼,也可以將這些重復(fù)的操作封裝到訪問(wèn)者中去迁霎。
缺陷
但是吱抚,訪問(wèn)者模式并不是那么完美,它也有著致命的缺陷:增加新的元素類比較困難考廉。通過(guò)訪問(wèn)者模式的代碼可以看到秘豹,在訪問(wèn)者類中,每一個(gè)元素類都有它對(duì)應(yīng)的處理方法昌粤,也就是說(shuō)既绕,每增加一個(gè)元素類都需要修改訪問(wèn)者類(也包括訪問(wèn)者類的子類或者實(shí)現(xiàn)類),修改起來(lái)相當(dāng)麻煩涮坐。也就是說(shuō)凄贩,在元素類數(shù)目不確定的情況下,應(yīng)該慎用訪問(wèn)者模式袱讹。所以疲扎,訪問(wèn)者模式比較適用于對(duì)已有功能的重構(gòu),比如說(shuō)捷雕,一個(gè)項(xiàng)目的基本功能已經(jīng)確定下來(lái)椒丧,元素類的數(shù)據(jù)已經(jīng)基本確定下來(lái)不會(huì)變了,會(huì)變的只是這些元素內(nèi)的相關(guān)操作非区,這時(shí)候瓜挽,我們可以使用訪問(wèn)者模式對(duì)原有的代碼進(jìn)行重構(gòu)一遍,這樣一來(lái)征绸,就可以在不修改各個(gè)元素類的情況下久橙,對(duì)原有功能進(jìn)行修改俄占。
總結(jié)
正如《設(shè)計(jì)模式》的作者GoF對(duì)訪問(wèn)者模式的描述:大多數(shù)情況下,你并需要使用訪問(wèn)者模式淆衷,但是當(dāng)你一旦需要使用它時(shí)缸榄,那你就是真的需要它了。當(dāng)然這只是針對(duì)真正的大牛而言祝拯。在現(xiàn)實(shí)情況下(至少是我所處的環(huán)境當(dāng)中)甚带,很多人往往沉迷于設(shè)計(jì)模式,他們使用一種設(shè)計(jì)模式時(shí)佳头,從來(lái)不去認(rèn)真考慮所使用的模式是否適合這種場(chǎng)景鹰贵,而往往只是想展示一下自己對(duì)面向?qū)ο笤O(shè)計(jì)的駕馭能力。編程時(shí)有這種心理康嘉,往往會(huì)發(fā)生濫用設(shè)計(jì)模式的情況碉输。所以,在學(xué)習(xí)設(shè)計(jì)模式時(shí)亭珍,一定要理解模式的適用性敷钾。必須做到使用一種模式是因?yàn)榱私馑膬?yōu)點(diǎn),不使用一種模式是因?yàn)榱私馑谋锥艘蘩妫欢皇鞘褂靡环N模式是因?yàn)椴涣私馑谋锥俗杌模皇褂靡环N模式是因?yàn)椴涣私馑膬?yōu)點(diǎn)。