1. 前文彙總
2. 引言
訪問者模式也可以說是所有設計模式中最難的一種設計模式了,當然我們平常也很少會用到它。設計模式的作者是這麼評價訪問者模式的:大多情況下,你並不需要使用訪問者模式,但是一旦需要使用它時,那就真的需要使用了。
3. 一個簡單的示例
又快到年底, CEO 和 CTO 開始評定員工一年的工作績效,員工分為工程師和經理, CTO 關注工程師的程式碼量、經理的新產品數量; CEO 關注的是工程師的KPI和經理的KPI以及新產品數量。
由於 CEO 和 CTO 對於不同員工的關注點是不一樣的,這就需要對不同員工型別進行不同的處理。訪問者模式此時可以派上用場了。
首先定義一個員工基類 Staff :
public abstract class Staff {
public String name;
// 員工KPI
public int kpi;
public Staff(String name) {
this.name = name;
kpi = new Random().nextInt(10);
}
// 核心方法,接受Visitor的訪問
abstract void accept(Visitor visitor);
}
Staff
類定義了員工基本資訊及一個 accept()
方法, accept()
方法表示接受訪問者的訪問,由子類具體實現。
而 Visitor
是個介面,傳入不同的實現類,可訪問不同的資料。
下面是工程師和經理的具體實現類:
public class Engineer extends Staff {
public Engineer(String name) {
super(name);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
// 工程師一年的程式碼數量
public int getCodeLines() {
return new Random().nextInt(10 * 10000);
}
}
public class Manager extends Staff {
public Manager(String name) {
super(name);
}
@Override
void accept(Visitor visitor) {
visitor.visit(this);
}
// 一年做的產品數量
public int getProducts() {
return new Random().nextInt(10);
}
}
工程師是程式碼數量,經理是產品數量,他們的職責不一樣,也就是因為差異性,才使得訪問模式能夠發揮它的作用。
下面是 Visitor 介面的定義:
public interface Visitor {
// 訪問工程師型別
void visit(Engineer engineer);
// 訪問經理型別
void visit(Manager manager);
}
Visitor 宣告瞭兩個 visit 方法,分別是對工程師和經理對訪問函式。
接下來定義兩個具體的訪問者: CEO 和 CTO 。
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());
}
}
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());
}
}
接著是一個報表類,公司的 CEO 和 CTO 通過這個報表檢視所有員工的業績:
public class BusinessReport {
private List<Staff> mStaffs = new LinkedList<>();
public BusinessReport() {
mStaffs.add(new Manager("經理-A"));
mStaffs.add(new Engineer("工程師-A"));
mStaffs.add(new Engineer("工程師-B"));
mStaffs.add(new Manager("經理-B"));
mStaffs.add(new Engineer("工程師-C"));
}
/**
* 為訪問者展示報表
* @param visitor 公司高層,如 CEO、CTO
*/
public void showReport(Visitor visitor) {
for (Staff staff : mStaffs) {
staff.accept(visitor);
}
}
}
最後是一個場景類:
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());
}
}
執行結果如下:
=========== CEO看報表 ===========
經理: 經理-A, KPI: 7, 新產品數量: 8
工程師: 工程師-A, KPI: 6
工程師: 工程師-B, KPI: 3
經理: 經理-B, KPI: 4, 新產品數量: 4
工程師: 工程師-C, KPI: 2
=========== CTO看報表 ===========
經理: 經理-A, 產品數量: 6
工程師: 工程師-A, 程式碼行數: 61280
工程師: 工程師-B, 程式碼行數: 10353
經理: 經理-B, 產品數量: 5
工程師: 工程師-C, 程式碼行數: 65827
4. 訪問者模式
4.1 定義
訪問者模式(Visitor Pattern) 是一個相對簡單的模式, 其定義如下:
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封裝一些作用於某種資料結構中的各元素的操作, 它可以在不改變資料結構的前提下定義作用於這些元素的新的操作。 )
4.2 通用類圖
- Visitor 抽象訪問者:抽象類或者介面,宣告訪問者可以訪問哪些元素。
- ConcreteVisitor 具體訪問者:它影響訪問者訪問到一個類後該怎麼幹, 要做什麼事情。
- Element 抽象元素:介面或者抽象類,宣告接受哪一類訪問者訪問。
- ConcreteElement 具體元素:實現方法。
- ObjectStruture 結構物件:元素產生者,一般容納在多個不同類、不同介面的容器。
4.3 通用程式碼
抽象元素:
public abstract class Element {
// 定義業務邏輯
abstract void doSomething();
// 定義允許訪問角色
abstract void accept(IVisitor visitor);
}
具體元素:
public class ConcreteElement1 extends Element{
@Override
void doSomething() {
}
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
public class ConcreteElement2 extends Element{
@Override
void doSomething() {
}
@Override
void accept(IVisitor visitor) {
visitor.visit(this);
}
}
抽象訪問者:
public interface IVisitor {
void visit(ConcreteElement1 ele1);
void visit(ConcreteElement2 ele2);
}
具體訪問者:
public class Visitor implements IVisitor{
@Override
public void visit(ConcreteElement1 ele1) {
ele1.doSomething();
}
@Override
public void visit(ConcreteElement2 ele2) {
ele2.doSomething();
}
}
結構物件:
public class ObjectStruture {
public static Element createElement() {
Random random = new Random();
if (random.nextInt(100) > 50) {
return new ConcreteElement1();
} else {
return new ConcreteElement2();
}
}
}
場景類:
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Element e1 = ObjectStruture.createElement();
e1.accept(new Visitor());
}
}
}
4.4 優點
-
各角色職責分離,符合單一職責原則。
通過UML類圖和上面的示例可以看出來,Visitor、ConcreteVisitor、Element 、ObjectStructure,職責單一,各司其責。
-
具有優秀的擴充套件性。
如果需要增加新的訪問者,增加實現類 ConcreteVisitor 就可以快速擴充套件。
-
使得資料結構和作用於結構上的操作解耦,使得操作集合可以獨立變化。
員工屬性(資料結構)和CEO、CTO訪問者(資料操作)的解耦。
-
靈活性。
4.5 缺點
-
具體元素對訪問者公佈細節,違反了迪米特原則。
CEO、CTO需要呼叫具體員工的方法。
-
具體元素變更時導致修改成本大。
變更員工屬性時,多個訪問者都要修改。
-
違反了依賴倒置原則,為了達到「區別對待」而依賴了具體類,沒有用來抽象。
訪問者 visit 方法中,依賴了具體員工的具體方法。