設計模式(23) 訪問者模式

由於應用開發過程中先前完成的類型會因爲需求變化(無論是業務功能,還是技術實現或是出於集成的需要)增加新的方法,如果直接在基類中增加新的方法,其派生類型可能需要相應進行比較繁瑣的處理。而使用訪問者模式可以做到在不改變既有類型層次的前提下,運行時動態爲類型層次的每個類增加新的操作。

訪問者模式

GOF對策略模式的描述爲: 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... — Design Patterns : Elements of Reusable Object-Oriented Software

UML類圖 訪問者模式包含五種角色:

  • IVistor(抽象訪問者):爲該對象結構中具體元素角色聲明一個訪問操作接口。
  • ConcreteVisitor(具體訪問者):每個具體訪問者都實現了IVistor中定義的操作。
  • IElement(抽象元素):定義了一個accept操作,以IVisitor作爲參數。
  • ConcreteElement(具體元素):實現了IElement中的accept()方法,調用IVistor的訪問方法以便完成對一個元素的操作。
  • ObjectStructure(對象結構):可以是組合模式,也可以是集合,能夠枚舉它包含的元素,並提供一個接口,允許IVistor訪問它的元素。

代碼示例

設想有這樣一個HR系統,系統只能按照標準的工作時間、時薪計算薪金,在系統交付後發現需要提供加班計算功能,而且還需要安排休假、晉升等功能,考慮到類似的需求在將來還會出現,所以改造的時候考慮採用訪問者模式。在HR系統的對象上增加了Accept某個IVisistor接口的能力,在添加新功能的時候可以實現IVisitor接口。

public interface IEmployee
{
    string Name { get; set; }
    double Income { get; set; }
    int VacationDays { get; set; }
    void Accept(IVisitor visitor);
}

public interface IVisitor
{
    void VisitiEmployee(IEmployee employee);
    void VisitManager(Manager manager);
}

public class Employee : IEmployee
{
    public string Name { get; set; }
    public double Income { get; set; }
    public int VacationDays { get; set; }
    public Employee(string name, double income, int vacationDays)
    {
        this.Name = name;
        this.Income = income;
        this.VacationDays = vacationDays;
    }
    public void Accept(IVisitor visitor)
    {
        visitor.VisitiEmployee(this);
    }
}

public class Manager : IEmployee
{
    public string Department { get; set; }
    public string Name { get; set; }
    public double Income { get; set; }
    public int VacationDays { get; set; }
    public Manager(string name, double income, int vacationDays, string department)
    {
        this.Name = name;
        this.Income = income;
        this.VacationDays = vacationDays;
        this.Department = department;
    }
    public void Accept(IVisitor visitor)
    {
        visitor.VisitManager(this);
    }
}

public class EmployeeCollection : List<IEmployee>
{
    public void Accept(IVisitor visitor)
    {
        foreach (IEmployee employee in this)
        {
            employee.Accept(visitor);
        }
    }
}

public class ExtraVacationVisitor : IVisitor
{
    public void VisitiEmployee(IEmployee employee)
    {
        employee.VacationDays += 1;
    }

    public void VisitManager(Manager manager)
    {
        manager.VacationDays += 2;
    }
}

public class RaiseSalaryVisitor : IVisitor
{
    public void VisitiEmployee(IEmployee employee)
    {
        employee.Income *= 1.1;
    }

    public void VisitManager(Manager manager)
    {
        manager.Income *= 1.2;
    }
}

調用端代碼

public class Test
{
    public static void Entry()
    {
        EmployeeCollection employees = new EmployeeCollection();
        employees.Add(new Employee("joe", 25000, 14));
        employees.Add(new Manager("alice", 22000, 14, "sales"));
        employees.Add(new Employee("peter", 15000, 7));

        employees.Accept(new ExtraVacationVisitor());
        employees.Accept(new RaiseSalaryVisitor());
    }
}

Employee類型並沒有加薪和修改休假天數的方法,但藉助訪問者模式,時期具有了對應的功能。訪問者模式的關鍵代碼是在數據基礎類裏面有一個方法接受訪問者,將自身引用傳入訪問者,這樣訪問者就可以操作數據類了。

訪問者模式的適用場景

  • 一個類型需要依賴於很多不同接口的類型,在結構儘量鬆散的前提下,希望可以用到這些類型不同接口方法。
  • 經常需要爲一個結構相對固定的對象結構添加一些新的操作。
  • 需要用一個獨立的類型來組織一批不相干的操作,使用它的類型可以根據應用需要進行定製。

訪問者模式的特點

優點

  • 符合單一職責原則。
  • 優秀的擴展性。
  • 靈活性。 缺點
  • 具體元素對訪問者公佈細節,違反了迪米特原則。
  • 具體元素變更比較困難。
  • 違反了依賴倒置原則,依賴了具體類,而不是依賴抽象。

參考書籍: 王翔著 《設計模式——基於C#的工程化實現及擴展》

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