關於 Visitor 本篇將討論以下幾個問題
1. 關於 Visitor
2. UML
3. 將 UML 轉為程式碼
4. 情境
測試環境:
OS:Windows 10
IDE:Visual Studio 2019
1. 關於 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.
by Gang of Four
- 表示在物件結構的元素(e.g. 性別類中的男、女兩個元素)中執行的操作(邏輯)
- 透過訪問者可在定義新操作(邏輯)時,無需更改執行操作(邏輯)的元素的類別
Visitor(訪問者)屬於行為型(Behavioral Patterns),當遇到類別內元素固定但操作邏輯時常變動時,可藉由 Visitor 將操作邏輯抽離元素,且在新增操作邏輯時不需修改元素本身。
優點:
- 符合 單一職責原則(Single Responsibility Principle)
- 符合 開閉原則(Open Closed Principle)
缺點:
- 新增元素則牽一髮動全身,全部 Visitor 都要修改
2. UML
Class 間關聯:
- Client 關聯 Visitor & ObjectStructure
- ObjectStructure 關聯 Element
- ConcreteVisitor 繼承 Visitor
- ConcreteElement 繼承 Element
Class:
- Client:呼叫端
- Visitor:訪問者的抽象類別或介面
- ConcreteVisitor:訪問者實作,定義元素的操作邏輯
- ObjectStructure:定義元素與欲執行的操作邏輯(Visitor)
- Element:元素的抽象類別或介面
- ConcreteEelement:元素實作,經由 ObjectStructure 指定操作邏輯(Visitor)
3. 將 UML 轉為程式碼
定義元素與欲執行的操作邏輯(Visitor)
/// <summary>
/// 定義元素與欲執行的操作邏輯(Visitor)
/// </summary>
public class ObjectStructure
{
private List<IElement> _elements = new List<IElement>();
public void Attach(IElement element)
{
_elements.Add(element);
}
public void Detach(IElement element)
{
_elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (IElement element in _elements)
{
element.Accept(visitor);
}
}
}
訪問者的介面
/// <summary>
/// 訪問者的介面
/// </summary>
public interface IVisitor
{
void VisitConcreteElementA(ConcreteElementA concreteElementA);
void VisitConcreteElementB(ConcreteElementB concreteElementB);
}
訪問者實作,定義元素的操作邏輯
/// <summary>
/// 訪問者實作,定義元素的操作邏輯
/// </summary>
public class ConcreteVisitor1 : IVisitor
{
public void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine($"{concreteElementA.GetType().Name} visited by {this.GetType().Name}");
}
public void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine($"{concreteElementB.GetType().Name} visited by {this.GetType().Name}");
}
}
/// <summary>
/// 訪問者實作,定義元素的操作邏輯
/// </summary>
public class ConcreteVisitor2 : IVisitor
{
public void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine($"{concreteElementA.GetType().Name} visited by {this.GetType().Name}");
}
public void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine($"{concreteElementB.GetType().Name} visited by {this.GetType().Name}");
}
}
元素的介面
/// <summary>
/// 元素的介面
/// </summary>
public interface IElement
{
void Accept(IVisitor visitor);
}
元素實作,經由 ObjectStructure 指定操作邏輯(Visitor)
/// <summary>
/// 元素實作,經由 ObjectStructure 指定操作邏輯(Visitor)
/// </summary>
public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementA(this);
}
public void OperationA()
{
}
}
/// <summary>
/// 元素實作,經由 ObjectStructure 指定操作邏輯(Visitor)
/// </summary>
public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.VisitConcreteElementB(this);
}
public void OperationB()
{
}
}
- 建立
objectStructure
- 將元素 A & B 附加至
objectStructure
- 指定操作邏輯(Visitor) 1 & 2
static void Main(string[] args)
{
Default.ObjectStructure objectStructure = new Default.ObjectStructure();
objectStructure.Attach(new Default.ConcreteElementA());
objectStructure.Attach(new Default.ConcreteElementB());
objectStructure.Accept(new Default.ConcreteVisitor1());
objectStructure.Accept(new Default.ConcreteVisitor2());
Console.ReadLine();
}
執行結果
ConcreteElementA visited by ConcreteVisitor1
ConcreteElementB visited by ConcreteVisitor1
ConcreteElementA visited by ConcreteVisitor2
ConcreteElementB visited by ConcreteVisitor2
4. 情境
我們接到了一依據是否營業顯示門市看板的需求
- 營業時間分為「營業中」與「非營業時間」
- 看板內容分為「折扣資訊」與「會員資訊」,且未來會新增
定義元素(是否營業)與欲顯示看板資訊
/// <summary>
/// 定義元素(是否營業)與欲顯示看板資訊
/// </summary>
public class ObjectStructure
{
private List<IElement> _elements = new List<IElement>();
public void Attach(IElement element)
{
_elements.Add(element);
}
public void Detach(IElement element)
{
_elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (IElement element in _elements)
{
element.Accept(visitor);
}
}
}
訪問者的介面
/// <summary>
/// 訪問者的介面
/// </summary>
public interface IVisitor
{
void OpenMessage(Open open);
void CloseMessage(Close close);
}
訪問者實作,折扣資訊 & 會員資訊
/// <summary>
/// 訪問者實作,折扣資訊
/// </summary>
public class DiscountMessage : IVisitor
{
public void OpenMessage(Open open)
{
Console.WriteLine($"營業中:今日咖啡第二杯半價");
}
public void CloseMessage(Close close)
{
Console.WriteLine($"非營業時間:X 月 X 日 咖啡買一送一");
}
}
/// <summary>
/// 訪問者實作,會員資訊
/// </summary>
public class MemberMessage : IVisitor
{
public void OpenMessage(Open open)
{
Console.WriteLine($"營業中:今日會員集點兩倍送");
}
public void CloseMessage(Close close)
{
Console.WriteLine($"非營業時間:會員申辦只要 200 元");
}
}
元素的介面
/// <summary>
/// 元素的介面
/// </summary>
public interface IElement
{
void Accept(IVisitor visitor);
}
元素實作,營業中 & 非營業時間
/// <summary>
/// 元素實作,營業中
/// </summary>
public class Open : IElement
{
public void Accept(IVisitor visitor)
{
visitor.OpenMessage(this);
}
}
/// <summary>
/// 元素實作,非營業時間
/// </summary>
public class Close : IElement
{
public void Accept(IVisitor visitor)
{
visitor.CloseMessage(this);
}
}
/// <summary>
/// 定義元素(是否營業)與欲顯示看板資訊
/// </summary>
public class ObjectStructure
{
private List<IElement> _elements = new List<IElement>();
public void Attach(IElement element)
{
_elements.Add(element);
}
public void Detach(IElement element)
{
_elements.Remove(element);
}
public void Accept(IVisitor visitor)
{
foreach (IElement element in _elements)
{
element.Accept(visitor);
}
}
}
- 建立
objectStructure
- 將元素 營業中 & 非營業時間 附加至
objectStructure
- 指定操作邏輯(Visitor) 折扣資訊 & 會員資訊
static void Main(string[] args)
{
Situation.ObjectStructure objectStructure = new Situation.ObjectStructure();
objectStructure.Attach(new Situation.Open());
objectStructure.Attach(new Situation.Close());
Console.WriteLine($"折扣資訊");
objectStructure.Accept(new Situation.DiscountMessage());
Console.WriteLine($"\n會員資訊");
objectStructure.Accept(new Situation.MemberMessage());
Console.ReadLine();
}
執行結果
折扣資訊
營業中:今日咖啡第二杯半價
非營業時間:X 月 X 日 咖啡買一送一
會員資訊
營業中:今日會員集點兩倍送
非營業時間:會員申辦只要 200 元
完整程式碼
GitHub:Behavioral_11_Visitor