深入Java設計模式之訪問者模式

訪問者模式介紹

最複雜的設計模式,並且使用頻率不高,《設計模式》的作者評價爲:大多情況下,你不需要使用訪問者模式,但是一旦需要使用它時,那就真的需要使用了。

訪問者模式是一種將數據操作和數據結構分離的設計模式。(覺得太抽象,可以看下面的例子)。

訪問者模式的使用場景

  1. 對象結構比較穩定,但經常需要在此對象結構上定義新的操作。
  2. 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而需要避免這些操作“污染”這些對象的類,也不希望在增加新操作時修改這些類。

角色介紹

  • 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());
    }
}

總結

我們要根據具體情況來評估是否適合使用訪問者模式,例如,我們的對象結構是否足夠穩定,是否需要經常定義新的操作,使用訪問者模式是否能優化我們的代碼,而不是使我們的代碼變得更復雜。

  • 訪問者模式的優點。

    1. 各角色職責分離,符合單一職責原則
      通過UML類圖和上面的示例可以看出來,Visitor、ConcreteVisitor、Element 、ObjectStructure,職責單一,各司其責。
    2. 具有優秀的擴展性
      如果需要增加新的訪問者,增加實現類 ConcreteVisitor 就可以快速擴展。
    3. 使得數據結構和作用於結構上的操作解耦,使得操作集合可以獨立變化
      員工屬性(數據結構)和CEO、CTO訪問者(數據操作)的解耦。
    4. 靈活性
  • 訪問者模式的缺點。

    1. 具體元素對訪問者公佈細節,違反了迪米特原則
      CEO、CTO需要調用具體員工的方法。
    2. 具體元素變更時導致修改成本大
      變更員工屬性時,多個訪問者都要修改。
    3. 違反了依賴倒置原則,爲了達到“區別對待”而依賴了具體類,沒有以來抽象
      訪問者 visit 方法中,依賴了具體員工的具體方法。

 示例代碼:

https://gitee.com/lzhcode/maven-parent/tree/master/lzh-technology/src/main/java/com/lzhsite/technology/designPattern/visitor

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章