可做用於對象結構中各個元素,在不改變各元素類的前提下,定義做用於這些元素新操做方法的一種行爲型設計模式。設計模式
抽象訪問者(Visitor): 聲明出對對象結構中每個具體元素的訪問方法visit,傳入Concrete Element對象做爲參數bash
具體訪問者(Concrete Visitor): 實現各類visit方法,調用具體元素對象完成對應的各類操做微信
元素(Element): 定義出抽象accept方法,用於接收Visitor對象參數框架
具體元素(Concrete Element): 實現accept操做,該操做通常用於操做執行Visitor.visit方法,將自身做爲參數傳入ide
對象結構(Object structure): 各個元素構成的一個總體,提供可以讓訪問者訪問全部元素的接口。能夠是集合(好比List),或者是複合的類對象。ui
抽象訪問者: 須要依賴於具體的元素類,而不是抽象元素類,以免if-else之類的嵌套和類型判斷this
public interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
複製代碼
具體訪問者:spa
public class ConcreteVisitorA implements Visitor{
@Override
public void visit(ConcreteElementA elementA) {
// use elementA to do something
}
@Override
public void visit(ConcreteElementB elementB) {
// use elementB to do something
}
}
public class ConcreteVisitorB implements Visitor{
@Override
public void visit(ConcreteElementA elementA) {
// use elementA to do something
}
@Override
public void visit(ConcreteElementB elementB) {
// use elementB to do something
}
}
複製代碼
抽象元素(可能接口或者抽象類): 由各個具體元素類實現accept接口,用於使得Visitor依賴於具體元素類設計
public interface Element {
void accept(Visitor visitor);
}
複製代碼
具體元素:code
public class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
複製代碼
Client中簡單調用:
List<Element> elements = new ArrayList<>();
elements.add(new ConcreteElementA());
elements.add(new ConcreteElementB());
elements.add(new ConcreteElementA());
ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
for (Element element : elements) {
element.accept(concreteVisitorA);
element.accept(concreteVisitorB);
}
複製代碼
公司對員工進行考覈,員工存在工程師和產品經理,考覈的評審有CEO和CTO,CTO只關注工程師的代碼量和產品經理的新產品數量。而CEO則關注工程師的KPI和產品經理的KPI及新產品數量。從這邊能夠看出CEO和CTO對員工考覈的關注點不同,這就須要對員工類型進行處理,就能夠用到訪問者模式(員工分類結構也是很穩定不變的)。(來自<<Android源碼設計模式解析與實踐>>)
員工Staff:
public abstract class Staff {
String name;
public Staff(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract int getKPI();
public abstract void accept(Visitor visitor);
}
複製代碼
工程師Engineer:
public class Engineer extends Staff{
public Engineer(String name) {
super(name);
}
@Override
public int getKPI() {
return 9;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getCodeLines() {
return 9999;
}
}
複製代碼
產品經理Manager:
public class Manager extends Staff{
public Manager(String name) {
super(name);
}
@Override
public int getKPI() {
return 10;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public int getProductCount() {
return 5;
}
}
複製代碼
訪問者Visitor:
public interface Visitor {
void visit(Engineer engineer);
void visit(Manager manager);
}
複製代碼
CEO:
public class CEOVisitor implements Visitor{
@Override
public void visit(Engineer engineer) {
System.out.println("CEOVisitor 考覈工程師"+ engineer.getName() + ",KPI:" + engineer.getKPI());
}
@Override
public void visit(Manager manager) {
// TODO Auto-generated method stub
System.out.println("CEOVisitor 考覈產品經理"+ manager.getName() + ",KPI:" + manager.getKPI() + ",產品數:" + manager.getProductCount());
}
}
複製代碼
CTO:
public class CTOVisitor implements Visitor{
@Override
public void visit(Engineer engineer) {
System.out.println("CTOVisitor 考覈工程師"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
// TODO Auto-generated method stub
System.out.println("CTOVisitor 考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
}
}
複製代碼
BussinessReport(對應Object structure):
public class BussinessReport {
List<Staff> staffs = new ArrayList<>();
public BussinessReport() {
staffs.add(new Engineer("小張"));
staffs.add(new Manager("小王"));
}
public void showReport(Visitor visitor) {
for (Staff staff : staffs) {
staff.accept(visitor);
}
}
}
複製代碼
Client調用:
//構建報表
BussinessReport bussinessReport = new BussinessReport();
//給CEO看
bussinessReport.showReport(new CEOVisitor());
//給CTO看
bussinessReport.showReport(new CTOVisitor());
複製代碼
若是不使用訪問者模式的話,也就不存在Visitor繼承體系,以上述具體例子爲例則須要對Staff的各類類型進行判斷: 假設在ReportUtils處理:
public class ReportUtils {
public static void visitCEO(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager)staff;
System.out.println("考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
} else if(staff instanceof Engineer) {
Engineer engineer = (Engineer)staff;
System.out.println("考覈工程師"+ engineer.getName() + ",KPI:" + engineer.getKPI());
}
}
public static void visitCTO(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager)staff;
System.out.println("考覈產品經理"+ manager.getName() + ",產品數:" + manager.getProductCount());
} else if(staff instanceof Engineer) {
Engineer engineer = (Engineer)staff;
System.out.println("考覈工程師"+ engineer.getName() + ",CodeLines:" + engineer.getCodeLines());
}
}
}
複製代碼
這就會致使出現一大堆的if -else嵌套和類型轉換,當類型較多的時候就會難以擴展和維護,而且當關注層面不一樣,重複的判斷會不少,難以維護後續持續擴展新的操做(visitXX,visitXXX...)。而使用訪問者Visitor模式,可以經過accept-visit方法的配套使用使得不一樣關注面的操做能夠分離,而且去除一大堆嵌套if-else和類型轉換的判斷,靈活性更好,可擴展性高,更易於維護。
角色職責分離,符合單一職責原則;
容許對組合結構中的各個元素加入新的操做,而不須要修改結構自己,增長新操做相對容易;
集中管理訪問者所進行操做的代碼;
具體元素元素對訪問者公佈細節,違反了迪米特原則(一個類對本身須要耦合或調用的類知道的最少);
增長刪減具體元素時修改爲本過大,須要修改Visitor繼承體系;
爲了達到"區別對待"而依賴具體類而不是抽象,違反了"依賴倒置"原則(高層模塊不該該依賴低層模塊,二者都應該依賴抽象);
一、對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。
二、須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,也不但願在增長新操做時修改這些類。 注意事項:訪問者能夠對功能進行統一,能夠作報表、UI、攔截器與過濾器。