一、介紹卿闹,定義
軟件系統(tǒng)中擁有一個由許多對象構(gòu)成的棒仍、比較穩(wěn)定的對象結(jié)構(gòu)泪酱,這些對象的類都擁有一個accept方法用來接受訪問者對象的訪問变骡。訪問者是一個接口,它擁有一個visit方法肢娘,這個方法對訪問的對象結(jié)構(gòu)中不同類型的元素作出不同的處理 呈础。在對象結(jié)構(gòu)的一次訪問過程中舆驶,我們遍歷整個對象結(jié)構(gòu),對每一個元素都實(shí)施accept方法而钞,在每一個元素的accept方法中會調(diào)用訪問者的visit方法沙廉,從而使訪問者得以處理對象結(jié)構(gòu)的每一個元素,我們可以針對對象結(jié)構(gòu)設(shè)計(jì)不同的訪問者類來完成不同的操作臼节,達(dá)到區(qū)別對待的效果撬陵。
封裝一些 作用于某種數(shù)據(jù)結(jié)構(gòu)中的各種元素的操作,它可以在不改變這個數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作网缝。
二巨税、使用場景
1.對象結(jié)構(gòu)比較穩(wěn)當(dāng),但經(jīng)常需要在此對象結(jié)構(gòu)上定義新的操作粉臊。
2.需要對一個對象結(jié)構(gòu)中的對象進(jìn)行很多不同的并且不相關(guān)的操作草添,而需要避免這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類扼仲。
三远寸、UML類圖
角色介紹
Visitor:接口或者抽象類,它定義了對每個元素(Element)訪問的行為屠凶,它的參數(shù)就是可以訪問的元素驰后,它的方法個數(shù)理論上與元素個數(shù)是一樣的。因此矗愧,訪問者模式要求元素的類族要穩(wěn)定倡怎,如果經(jīng)常添加、移除元素類贱枣,必然會頻繁修改Visitor接口,如果出現(xiàn)這種情況颤专,則說明不適合使用訪問者模式纽哥。
ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產(chǎn)生的具體行為栖秕。
Element:元素接口或者抽象類春塌,它提供接受訪問者(accept)方法,其意義是指每一個元素都要可以被訪問者訪問簇捍。
ElementA只壳、ElementB:具體的元素類,它提供接受訪問方法的具體實(shí)現(xiàn)暑塑,而這個具體的實(shí)現(xiàn)吼句,通常情況下是使用訪問者提供的訪問該元素的方法。
ObjectStructure:定義當(dāng)中所提到的對象結(jié)構(gòu)事格,對象結(jié)構(gòu)是一個抽象描述惕艳,它內(nèi)部管理了元素的集合搞隐,并且可以迭代這些元素供訪問者訪問。
四远搪、簡單實(shí)現(xiàn)
年終業(yè)績考核時劣纲,員工分為工程師和經(jīng)理,評定員工的分為CEO和CTO谁鳍,CTO只關(guān)注工程師的代碼量癞季、經(jīng)理的新產(chǎn)品數(shù)量;CEO只關(guān)心工程師的KPI和經(jīng)理的KPI以及新產(chǎn)品數(shù)量倘潜,從中可以看出對不同員工關(guān)注點(diǎn)是不同的绷柒,這就需要對不同員工不同處理。
/* 員工基類
* Staff類定義了員工的基本信息以及一個accept方法窍荧,accept方法表示接受訪問者的訪問辉巡,由子類具體實(shí)現(xiàn)。
*/
public abstract class Staff {
public String name;
public int kpi; //員工kpi
public Staff(String aName) {
this.name = aName;
kpi = new Random().nextInt(10);
}
//接受Visitor的訪問
public abstract void accept(Visitor visitor);
}
工程師和經(jīng)理類
/**
* 工程師類中添加了獲取代碼行數(shù)的函數(shù)蕊退,而經(jīng)理類型中則添加了獲取新產(chǎn)品數(shù)量的函數(shù)郊楣,
* 它們的職責(zé)是不一樣的,也正是它們的差異性瓤荔,才使得訪問者模式能夠發(fā)揮它是作用净蚤。
* Staff、Engineer输硝、Manager3個類型就是對象結(jié)構(gòu)今瀑,這些類型相對穩(wěn)定,不會發(fā)生變化点把。
*/
public class Engineer extends Staff {
public Engineer(String aName) {
super(aName);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//工程師這一年寫的代碼數(shù)量
public int getCodeLines() {
return new Random().nextInt(10*10000);
}
}
public class Manager extends Staff {
private int products;// 產(chǎn)品數(shù)量
public Manager(String aName) {
super(aName);
products = new Random().nextInt(10);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
//一年內(nèi)做的產(chǎn)品數(shù)量
public int getProducts() {
return products;
}
}
public interface Visitor {
//訪問工程師類型
public void visit(Engineer engineer);
//訪問經(jīng)理類型
public void visit(Manager manager);
}
然后將這些員工添加到一個業(yè)務(wù)報表類中橘荠,公司高層通過該報表類的showReport函數(shù)查看所有員工的業(yè)績
public class BusinessReport {
List<Staff> mStaffs = new LinkedList<Staff>();
public BusinessReport() {
mStaffs.add(new Manager("manager-1"));
mStaffs.add(new Engineer("engineer-1"));
mStaffs.add(new Engineer("engineer-2"));
mStaffs.add(new Engineer("engineer-3"));
mStaffs.add(new Manager("manager-2"));
}
/**
* 為訪問者展示報表
* @param visitor CEO或者CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : mStaffs) {
staff.accept(visitor);
}
}
}
如果不使用訪問者模式,只用一個visit函數(shù)處理郎逃,就需要在其中對不同員工類型進(jìn)行判斷哥童,然后分別處理:
class ReportUtil{
public void visit(Staff staff){
if(staff instanceof Manager){
Manager mgr = (Manager)staff;
System.out.println("經(jīng)理:"+mgr.name+",KPI:"+mgr.kpi+",新產(chǎn)品數(shù)量"+mgr.getProducts());
}else{
Engineer eng= (Engineer)staff;
System.out.println("工程師:"+eng.name+",KPI:"+eng.kpi);
}
}
}
這就導(dǎo)致了if-else邏輯潛逃和強(qiáng)制類型轉(zhuǎn)換,難易擴(kuò)展和維護(hù)褒翰,當(dāng)類型較多時變得復(fù)雜贮懈。而使用訪問者模式,使得結(jié)構(gòu)更加清晰优训,靈活性高朵你。
在CEO的訪問者中,CEO只關(guān)注Engineer員工的KPI揣非,而對于Manager類型的員工除了KPI之外抡医,還有該Manager本年度新開發(fā)產(chǎn)品的數(shù)量。
兩類員工關(guān)注點(diǎn)不同早敬,通過兩個visitor方法分別進(jìn)行處理魂拦。
public class CEOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
Log.v(MainActivity.TAG, "engineer:" + engineer.name + ", KPI:" + engineer.kpi);
}
@Override
public void visit(Manager manager) {
Log.v(MainActivity.TAG, "mananger:" + manager.name + ", KPI:" + manager.kpi + "毛仪,新產(chǎn)品數(shù)量:" + manager.getProducts());
}
}
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
Log.v(MainActivity.TAG, "engineer:"+ engineer.name + ",codeslines:" + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
Log.v(MainActivity.TAG, "manager:" + manager.name + "productsNumber:" + manager.getProducts());
}
}
public class MainActivity extends AppCompatActivity {
public static String TAG = "VisitorLOG";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//構(gòu)建報表
BusinessReport report = new BusinessReport();
Log.v(TAG, "--------CEO visit--------------");
//設(shè)置訪問者,這里是CEO
report.showReport(new CEOVisitor());
Log.v(TAG, "--------CTO visit-------------");
//注入另一個訪問者, CTO
report.showReport(new CTOVisitor());
}
}
結(jié)果
V/VisitorLOG: --------CEO visit--------------
V/VisitorLOG: mananger:manager-1, KPI:0芯勘,新產(chǎn)品數(shù)量:5
V/VisitorLOG: engineer:engineer-1, KPI:3
V/VisitorLOG: engineer:engineer-2, KPI:6
V/VisitorLOG: engineer:engineer-3, KPI:7
V/VisitorLOG: mananger:manager-2, KPI:1箱靴,新產(chǎn)品數(shù)量:5
V/VisitorLOG: --------CTO visit-------------
V/VisitorLOG: manager:manager-1productsNumber:5
V/VisitorLOG: engineer:engineer-1,codeslines:62026
V/VisitorLOG: engineer:engineer-2,codeslines:34320
V/VisitorLOG: engineer:engineer-3,codeslines:53385
V/VisitorLOG: manager:manager-2productsNumber:5
五、模式的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):
各角色職責(zé)分離荷愕,符合單一職責(zé)原則衡怀;
具有優(yōu)秀的擴(kuò)展性
使得數(shù)據(jù)結(jié)構(gòu)和作用于結(jié)構(gòu)上的操作解耦,使得操作集合可以獨(dú)立變化
靈活性
缺點(diǎn):
具體元素對訪問者公布細(xì)節(jié)安疗,違反了迪米特原則
具體元素變更時導(dǎo)致修改成本加大抛杨;
違反了依賴倒置原則,為了達(dá)到“區(qū)別對待”而依賴了具體類荐类,沒有依賴抽象怖现。