設計模式之美:Visitor(訪問者)


索引

意圖

表示一個作用於某對象結構中的各元素的操作。

Visitor 使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。

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.

結構

參與者

Visitor

  • 為該對象結構中 ConcreteElement 的每一個類聲明一個 Visit 操作。該操作的名字和特征標識了發送 Visit 請求給該訪問者的那個類。

ConcreteVisitor

  • 實現每個由 Visitor 聲明的操作。

Element

  • 定義一個 Accept 操作,它以一個 Visitor 為參數。

ConcreteElement

  • 實現 Accept 操作,該操作以一個 Visitor 為參數。

ObjectStructure

  • 能枚舉 Element。
  • 可以提供一個高層的接口以允許該 Visitor 訪問它的元素。
  • 可以是一個 Composite 或是一個集合、列表或無序集合。

適用性

在以下情況下可以使用 Visitor 模式:

  • 一個對象結構包含很多類操作,它們有不同的接口,而你想對這些對象實施一些依賴於其具體類的操作。
  • 需要對一個對象結構中的對象進行很多不同的並且不相關的操作,而你想避免讓這些操作污染這些對象的類。
  • 定義對象結構的類很少改變,但經常需要在此結構上定義新的操作。

缺點

  • 增加新的 ConcreteElement 類很困難。添加新的 ConcreteElement 都要在 Visitor 中添加一個新的抽象操作。
  • 可能破壞封裝。Visitor 假定 ConcreteElement 接口的功能足夠強,足以讓 Visitor 進行它的工作。但有時會迫使你提供 ConcreteElement 的內部狀態的公共操作。

效果

  • Visitor 模式使得易於增加新的操作。
  • Visitor 集中相關的操作而分離無關的操作。

相關模式

  • Visitor 可以用於對一個 Composite 模式定義的對象結構進行操作。
  • Visitor 可以用於 Interpreter 解釋。

實現

實現方式(一):Visitor 模式結構樣式代碼。

  1 namespace VisitorPattern.Implementation1
  2 {
  3   public abstract class Element
  4   {
  5     public abstract void Accept(Visitor visitor);
  6   }
  7 
  8   public abstract class Visitor
  9   {
 10     public abstract void Visit(ConcreteElementA element);
 11     public abstract void Visit(ConcreteElementB element);
 12   }
 13 
 14   public class ObjectStructure
 15   {
 16     private List<Element> _elements = new List<Element>();
 17 
 18     public void Attach(Element element)
 19     {
 20       _elements.Add(element);
 21     }
 22 
 23     public void Detach(Element element)
 24     {
 25       _elements.Remove(element);
 26     }
 27 
 28     public void Accept(Visitor visitor)
 29     {
 30       foreach (var element in _elements)
 31       {
 32         element.Accept(visitor);
 33       }
 34     }
 35   }
 36 
 37   public class ConcreteElementA : Element
 38   {
 39     public string Name { get; set; }
 40 
 41     public override void Accept(Visitor visitor)
 42     {
 43       visitor.Visit(this);
 44     }
 45   }
 46 
 47   public class ConcreteElementB : Element
 48   {
 49     public string ID { get; set; }
 50 
 51     public override void Accept(Visitor visitor)
 52     {
 53       visitor.Visit(this);
 54     }
 55   }
 56 
 57   public class ConcreteVisitorA : Visitor
 58   {
 59     public override void Visit(ConcreteElementA element)
 60     {
 61       Console.WriteLine(
 62         "ConcreteVisitorA visited ConcreteElementA : {0}", 
 63         element.Name);
 64     }
 65 
 66     public override void Visit(ConcreteElementB element)
 67     {
 68       Console.WriteLine(
 69         "ConcreteVisitorA visited ConcreteElementB : {0}", 
 70         element.ID);
 71     }
 72   }
 73 
 74   public class ConcreteVisitorB : Visitor
 75   {
 76     public override void Visit(ConcreteElementA element)
 77     {
 78       Console.WriteLine(
 79         "ConcreteVisitorB visited ConcreteElementA : {0}", 
 80         element.Name);
 81     }
 82 
 83     public override void Visit(ConcreteElementB element)
 84     {
 85       Console.WriteLine(
 86         "ConcreteVisitorB visited ConcreteElementB : {0}", 
 87         element.ID);
 88     }
 89   }
 90 
 91   public class Client
 92   {
 93     public void TestCase1()
 94     {
 95       var objectStructure = new ObjectStructure();
 96 
 97       objectStructure.Attach(new ConcreteElementA());
 98       objectStructure.Attach(new ConcreteElementB());
 99 
100       objectStructure.Accept(new ConcreteVisitorA());
101       objectStructure.Accept(new ConcreteVisitorB());
102     }
103   }
104 }

實現方式(二):使用 Visitor 模式解構設計。

假設我們有一個 Employee 類,Employee 分為按時薪計算的 Employee 和按月薪計算的 Employee。

 1   public class Employee
 2   {
 3     public abstract string GetHoursAndPayReport();
 4   }
 5 
 6   public class HourlyEmployee : Employee
 7   {
 8     public override string GetHoursAndPayReport()
 9     {
10       // generate the line for this hourly employee
11       return "100 Hours and $1000 in total.";
12     }
13   }
14 
15   public class SalariedEmployee : Employee
16   {
17     public override string GetHoursAndPayReport()
18     {
19       // do nothing
20       return string.Empty;
21     }
22   }

這段代碼的問題是,Employee 類及子類耦合了 Salary Report 相關的職責,這侵犯了單一職責原則(Single Responsibility Principle),因為其導致每次需要更改 Report 相關的職責時,都需要修改 Employee 類。

我們是用 Visitor 模式來解決這個問題。

 1 namespace VisitorPattern.Implementation2
 2 {
 3   public abstract class Employee
 4   {
 5     public abstract string Accept(EmployeeVisitor visitor);
 6   }
 7 
 8   public class HourlyEmployee : Employee
 9   {
10     public override string Accept(EmployeeVisitor visitor)
11     {
12       return visitor.Visit(this);
13     }
14   }
15 
16   public class SalariedEmployee : Employee
17   {
18     public override string Accept(EmployeeVisitor visitor)
19     {
20       return visitor.Visit(this);
21     }
22   }
23 
24   public abstract class EmployeeVisitor
25   {
26     public abstract string Visit(HourlyEmployee employee);
27     public abstract string Visit(SalariedEmployee employee);
28   }
29 
30   public class HoursPayReport : EmployeeVisitor
31   {
32     public override string Visit(HourlyEmployee employee)
33     {
34       // generate the line of the report.
35       return "100 Hours and $1000 in total.";
36     }
37 
38     public override string Visit(SalariedEmployee employee)
39     {
40       // do nothing
41       return string.Empty;
42     }
43   }
44 }

實現方式(三):使用 Acyclic Visitor 模式解構設計。

我們注意到 Employee 類依賴於 EmployeeVisitor 基類。而 EmployeeVisitor 類為每個 Employee 的子類都提供了一個 Visit 方法。

因此,這里形成了一個依賴關系的環。這導致 Visitor 在響應變化時變得復雜。

Visitor 模式在類繼承關系不是經常變化時可以工作的很好,但在子類衍生頻繁的情況下會增加復雜度。

此時,我們可以應用 Acyclic Visitor 模式,抽象出窄接口,以使 Employee 子類僅依賴於該窄接口。

 1 namespace VisitorPattern.Implementation3
 2 {
 3   public abstract class Employee
 4   {
 5     public abstract string Accept(EmployeeVisitor visitor);
 6   }
 7 
 8   public class HourlyEmployee : Employee
 9   {
10     public override string Accept(EmployeeVisitor visitor)
11     {
12       try
13       {
14         IHourlyEmployeeVisitor hourlyEmployeeVisitor = (IHourlyEmployeeVisitor)visitor;
15         return hourlyEmployeeVisitor.Visit(this);
16       }
17       catch (InvalidCastException ex)
18       {
19         Console.WriteLine(ex.Message);
20       }
21 
22       return string.Empty;
23     }
24   }
25 
26   public class SalariedEmployee : Employee
27   {
28     public override string Accept(EmployeeVisitor visitor)
29     {
30       try
31       {
32         ISalariedEmployeeVisitor salariedEmployeeVisitor = (ISalariedEmployeeVisitor)visitor;
33         return salariedEmployeeVisitor.Visit(this);
34       }
35       catch (InvalidCastException ex)
36       {
37         Console.WriteLine(ex.Message);
38       }
39 
40       return string.Empty;
41     }
42   }
43 
44   public interface IHourlyEmployeeVisitor
45   {
46     string Visit(HourlyEmployee employee);
47   }
48 
49   public interface ISalariedEmployeeVisitor
50   {
51     string Visit(SalariedEmployee employee);
52   }
53 
54   public abstract class EmployeeVisitor
55   {
56   }
57 
58   public class HoursPayReport : EmployeeVisitor, IHourlyEmployeeVisitor
59   {
60     public string Visit(HourlyEmployee employee)
61     {
62       // generate the line of the report.
63       return "100 Hours and $1000 in total.";
64     }
65   }
66 
67   public class SalariedPayReport : EmployeeVisitor, ISalariedEmployeeVisitor
68   {
69     public string Visit(SalariedEmployee employee)
70     {
71       return "Something";
72     }
73   }
74 }

參考文章

設計模式之美》為 Dennis Gao 發布於博客園的系列文章,任何未經作者本人同意的人為或爬蟲轉載均為耍流氓。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM