有一天,技術總監說要知道所有技術員工的工作狀況,隔天,老闆說要知道所有員工的業績,再有一天,HR總監要知道所有員工的薪水。每一次都是利用組合模式遍歷出員工後取得員工的相關資訊。也許你會說,直接把所有的資訊全輸出就行了,那估計你要被老闆叼了,老闆就想知道業績,你把一個大表給看找,好吧,你可以回家了!讓訪客模式來幫我們解決這個問題,類別圖如下:
可以看出,有個visitor,這就是訪客,貌似有點像代理的感覺。在講代理模式就已經提到訪客模式本選購是在更特殊的場合採用了代理模式。實現代碼:
<?php interface IVisitor{ public function visitCommonEmployee( CommonEmployee $commonEmployee ); public function visitManager( Manager $manager ); } class Visitor implements IVisitor{ public function visitCommonEmployee( CommonEmployee $commonEmployee ) { echo $this->getCommonEmployee( $commonEmployee ); } public function visitManager( Manager $manager ) { echo $this->getManagerInfo( $manager ); } private function getBasicInfo( Employee $employee ) { $info = "姓名:".$employee->getName()."\t"; return $info; } private function getCommonEmployee( CommonEmployee $commonEmployee ) { $basicInfo = $this->getBasicInfo( $commonEmployee ); $otherInfo = "工作:".$commonEmployee->getJob()."\n"; return $basicInfo.$otherInfo; } private function getManagerInfo( Manager $manager ) { $basicInfo = $this->getBasicInfo( $manager ); $otherInfo = "业绩:".$manager->getPerformance()."\n"; return $basicInfo.$otherInfo; } } abstract class Employee { private $name; public function getName() { return $this->name; } public function setName( $name ) { $this->name = $name; } public function accept( IVisitor $visitor ) { $method = 'visit'.get_class( $this ); $visitor->$method( $this ); } } class CommonEmployee extends Employee{ private $job; public function getJob() { return $this->job; } public function setJob( $job ) { $this->job = $job; } } class Manager extends Employee{ private $performance; public function getPerformance() { return $this->performance; } public function setPerformance( $performance ) { $this->performance = $performance; } } $empList = array(); $zhangsan = new CommonEmployee(); $zhangsan->setName( '张三' ); $zhangsan->setJob( 'coding' ); $empList[] = $zhangsan; $lisi = new CommonEmployee(); $lisi->setName( '李四' ); $lisi->setJob( 'coding' ); $empList[] = $lisi; $wangwu = new Manager(); $wangwu->setName( '马云' ); $wangwu->setPerformance( '负值,但拍马屁厉害' ); $empList[] = $wangwu; foreach ($empList as $value) { $value->accept(new Visitor()); } ?>
運作結果:
姓名:張三 工作:coding
姓名:李四 工作:coding
姓名:馬雲 業績:負值,但拍馬屁以後如果有什麼特殊的列表,只需要增加一個特殊的visitor即可,這下老闆不能虐待我!
訪問者模式的定義
封裝一些作用於某種資料結構中的各元素的操作,它可以不改變資料結構的前提下定義作用於這些元素的新的操作。主要角色有:
1、VIsitor——抽象訪問者
抽象類者接口,聲明訪問者可以訪問哪些元素,具體到程序就是visit方法的參數定義哪些對像是可以被訪問的。
2、Concrete Visitor——具體訪客
它影響訪客訪問到一個類別後該怎麼幹,要做什麼事情。
3、Element——抽像元素
介面或抽象類,聲明接受哪一類訪問者訪問,程式上是透過accept方法中的參數來定義的。
4、ConcreteElement——具體元素
實作accept方法,通常是visitor->visitor($this),基本上都形成了一種模式了。
5、ObjectStruture——結構物件
元素產生者,一般容納多個不同類,不同介面的容器。
訪問者模式的優點1、符合單一職責原則
具體元素角色也就是Employee抽象類別的兩個子類別負責資料的加載,而Visitor類別則負責報表的展現,兩個不同的職責非常明確地分離開來,各自演繹變化。
2、優秀擴充性
由於職責分開,繼續增加對資料的操作是非常快速的,例如現在要增加一份給大老闆的報表,直接在Visitor中增加一個方法,傳遞資料後進行整理列印。
3、靈活性非常高
例如假設在報表的同時需要算工資的總和
訪問者模式的缺點
1、具體元素對訪客公佈細節
訪客要訪問一個類就必然要求這個類別公佈一些方法和數據,也就是說訪客關注了其他的類別的內部細節,這是迪米特法則所不建議的。
2、具體元素變更比較困難
具體元素角色的增加、刪除、修改都是比較困難的,就上面的例子,如果要增加一個成員變量,如年齡age ,Visitor就需要修改,如果Visitor不止一個,那是不是每個都要改?
3、違背了依賴倒置原則
訪問者依賴的是具體元素,而不是抽像元素,這破壞了依賴倒置原則,特別是在面向對象的編程中,拋棄了對接口的依賴,而直接依賴實作類別、擴充比較難。
訪問者模式的使用場景
1、一個對象結構包含很多類對象,它們有不同的接口,而你想對這些對象實施一些依賴於其具體類的操作,也就是說迭代器模式已經不能勝任的情景。
2、需要對一個物件結構中的物件進行許多不同且不相關的操作,而你想避免讓這些操作「污染」這些物件的類別
訪客模式的擴充1、統計功能
例如假設要統計所有員工的工資,只需要在介面增加一個方法,具體類別實作即可。
2、多個訪客
正如我前面所說,如果不同訪客報表要求不同,那麼直接新增一個Visitor類別實作即可。
3、雙分派(JAVA)
嘮叨:訪客模式是一種集中規整模式,特別適用於大規模重構,在這一階段需求已經非常清晰,原系統的功能點也已經明確,透過訪客模式可以容易把一些功能進行梳理,達到最終目的-功能集中化,例如一個統一的報表運算,UI展現等,還可以與其他模式混紡建立一套自己的過慮器或攔截器。