訪問者模式介紹
最複雜的設計模式,並且使用頻率不高,《設計模式》的作者評價爲:大多情況下,你不需要使用訪問者模式,但是一旦需要使用它時,那就真的需要使用了。
訪問者模式是一種將數據操作和數據結構分離的設計模式。(覺得太抽象,可以看下面的例子)。
訪問者模式的使用場景
- 對象結構比較穩定,但經常需要在此對象結構上定義新的操作。
- 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而需要避免這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類。
角色介紹
- Visitor:接口或者抽象類,定義了對每個 Element 訪問的行爲,它的參數就是被訪問的元素,它的方法個數理論上與元素的個數是一樣的,因此,訪問者模式要求元素的類型要穩定,如果經常添加、移除元素類,必然會導致頻繁地修改 Visitor 接口,如果出現這種情況,則說明不適合使用訪問者模式。
- ConcreteVisitor:具體的訪問者,它需要給出對每一個元素類訪問時所產生的具體行爲。
- Element:元素接口或者抽象類,它定義了一個接受訪問者(accept)的方法,其意義是指每一個元素都要可以被訪問者訪問。
- ElementA、ElementB:具體的元素類,它提供接受訪問的具體實現,而這個具體的實現,通常情況下是使用訪問者提供的訪問該元素類的方法。
- ObjectStructure:定義當中所提到的對象結構,對象結構是一個抽象表述,它內部管理了元素集合,並且可以迭代這些元素提供訪問者訪問。
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
/**
*
* 年底,CEO和CTO開始評定員工一年的工作績效,員工分爲工程師和經理,
* CTO關注工程師的代碼量、經理的新產品數量;CEO關注的是工程師的KPI和經理的KPI以及新產品數量。
* 由於CEO和CTO對於不同員工的關注點是不一樣的,這就需要對不同員工類型進行不同的處理。
* 訪問者模式此時可以派上用場了。
* @author lzhcode
*
*/
public class Client {
public static void main(String[] args) {
// 構建報表
BusinessReport report = new BusinessReport();
System.out.println("=========== CEO看報表 ===========");
report.showReport(new CEOVisitor());
System.out.println("=========== CTO看報表 ===========");
report.showReport(new CTOVisitor());
}
}
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
import java.util.Random;
//員工基類
public abstract class Staff {
public String name;
public int kpi;// 員工KPI
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
// 核心方法,接受Visitor的訪問
public abstract void accept(Visitor visitor);
}
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
import java.util.Random;
//經理
public class Manager extends Staff {
public Manager(String name) {
super(name);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
// 一年做的產品數量
public int getProducts() {
return new Random().nextInt(10);
}
}
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
public interface Visitor {
// 訪問工程師類型
void visit(Engineer engineer);
// 訪問經理類型
void visit(Manager manager);
}
public class ReportUtil {
public void visit(Staff staff) {
if (staff instanceof Manager) {
Manager manager = (Manager) staff;
System.out.println("經理: " + manager.name + ", KPI: " + manager.kpi +
", 新產品數量: " + manager.getProducts());
} else if (staff instanceof Engineer) {
Engineer engineer = (Engineer) staff;
System.out.println("工程師: " + engineer.name + ", KPI: " + engineer.kpi);
}
}
}
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
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("經理: " + manager.name + ", KPI: " + manager.kpi + ", 新產品數量: " + manager.getProducts());
}
}
在CEO的訪問者中,CEO關注工程師的 KPI,經理的 KPI 和新產品數量,
通過兩個 visitor 方法分別進行處理。如果不使用 Visitor 模式,只通過一個 visit 方法進行處理,
那麼就需要在這個 visit 方法中進行判斷,然後分別處理,代碼大致如下:
這就導致了 if-else 邏輯的嵌套以及類型的強制轉換,難以擴展和維護,當類型較多時,
這個 ReportUtil 就會很複雜。而使用 Visitor 模式,通過同一個函數對不同對元素類型進行相應對處理,
使結構更加清晰、靈活性更高。
package com.lzhsite.technology.designPattern.visitor.StaffDemo;
public class CTOVisitor implements Visitor {
@Override
public void visit(Engineer engineer) {
System.out.println("工程師: " + engineer.name + ", 代碼行數: " + engineer.getCodeLines());
}
@Override
public void visit(Manager manager) {
System.out.println("經理: " + manager.name + ", 產品數量: " + manager.getProducts());
}
}
總結
我們要根據具體情況來評估是否適合使用訪問者模式,例如,我們的對象結構是否足夠穩定,是否需要經常定義新的操作,使用訪問者模式是否能優化我們的代碼,而不是使我們的代碼變得更復雜。
-
訪問者模式的優點。
- 各角色職責分離,符合單一職責原則
通過UML類圖和上面的示例可以看出來,Visitor、ConcreteVisitor、Element 、ObjectStructure,職責單一,各司其責。 - 具有優秀的擴展性
如果需要增加新的訪問者,增加實現類 ConcreteVisitor 就可以快速擴展。 - 使得數據結構和作用於結構上的操作解耦,使得操作集合可以獨立變化
員工屬性(數據結構)和CEO、CTO訪問者(數據操作)的解耦。 - 靈活性
- 各角色職責分離,符合單一職責原則
-
訪問者模式的缺點。
- 具體元素對訪問者公佈細節,違反了迪米特原則
CEO、CTO需要調用具體員工的方法。 - 具體元素變更時導致修改成本大
變更員工屬性時,多個訪問者都要修改。 - 違反了依賴倒置原則,爲了達到“區別對待”而依賴了具體類,沒有以來抽象
訪問者 visit 方法中,依賴了具體員工的具體方法。
- 具體元素對訪問者公佈細節,違反了迪米特原則
示例代碼: