在上一篇介紹了 Visitor 模式,在不常新增衍生類別時,它會是一個好方法。但如果今天要新增一個飛機類別,要在 IVisitor
定義一個新方法及在 Visitor
類別中實作該方法,新增完後會需要將既有的類別重新編譯,很不方便。
Acyclic Visitor Pattern 解決了這個問題,我們可以將 IVisitor
介面退化,讓它不包含任何方法,然後各個類別各自建立自己的介面。
Acyclic Visitor 模式
相較於上一篇的 IVisitor
,IAcyclicVisitor
沒有包含任何方法,而是各個類別建立各自的介面,如下所示:
1 2 3 4 5 6 7 8 9 10 11
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public interface IAcyclicVisitor { } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public interface IMotorVisitor : IAcyclicVisitor { void Visit(AcyclicMotor motor); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public interface IBusVisitor : IAcyclicVisitor { void Visit(AcyclicBus bus); } }
|
巴士、機車類別的實作跟上一篇差不多,只是在呼叫 Visit
方法前,要先判斷是否為該類別之介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public interface IAcyclicTransportation { decimal Power { get; set; }
int NumberOfPassenger { get; set; }
bool HasAirConditioner { get; set; }
void Accept(IAcyclicVisitor visitor); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public class AcyclicMotor : IAcyclicTransportation { public decimal Power { get; set; }
public int NumberOfPassenger { get; set; }
public bool HasAirConditioner { get; set; }
public AcyclicMotor(decimal power, int numberOfPassenger, bool hasAirConditioner) { this.Power = power; this.NumberOfPassenger = numberOfPassenger; this.HasAirConditioner = hasAirConditioner; }
public void Accept(IAcyclicVisitor visitor) { if (visitor is IMotorVisitor) { (visitor as IMotorVisitor).Visit(this); } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public class AcyclicBus : IAcyclicTransportation { public decimal Power { get; set; }
public int NumberOfPassenger { get; set; }
public bool HasAirConditioner { get; set; }
public AcyclicBus(decimal power, int numberOfPassenger, bool hasAirConditioner) { this.Power = power; this.NumberOfPassenger = numberOfPassenger; this.HasAirConditioner = hasAirConditioner; }
public void Accept(IAcyclicVisitor visitor) { if (visitor is IBusVisitor) { (visitor as IBusVisitor).Visit(this); } } } }
|
最後是實作 AcyclicVisitor 類別,除了要繼承巴士、機車相對應的介面並實作,其他地方跟之前的一樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
using System;
namespace DemoCode.DesignPattern.Visitor.AcyclicVisitor { public class AcyclicVisitor : IBusVisitor, IMotorVisitor { public AcyclicVisitor() { }
public void Visit(AcyclicMotor motor) { Console.WriteLine($"這是台機車,我只在意馬力:{motor.Power}"); }
public void Visit(AcyclicBus bus) { var ifHasAirConditioner = bus.HasAirConditioner == true ? "有冷氣" : "沒有冷氣";
Console.WriteLine($"這是台公車,我只在意限乘人數:{bus.NumberOfPassenger}," + $"以及有沒有冷氣:{ifHasAirConditioner}"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Program { static void Main(string[] args) { var bus = new AcyclicBus(1000, 20, true); var motor = new AcyclicMotor(100, 2, false);
var visitor = new AcyclicVisitor(); bus.Accept(visitor); motor.Accept(visitor); } }
|
最後輸出結果如下,跟前一篇結果相同:
1 2
| 我是台公車,我只在意限乘人數:20,以及有沒有冷氣:有冷氣 我是台機車,我只在意馬力:100
|
完整程式碼請參考
結論
Acyclic Visitor 模式將巴士、機車的介面獨立出來,未來若要新增飛機類別,只要新增飛機的介面、類別,以及在 AcyclicVisitor 實作飛機的 Visit 方法即可。
在【無瑕的程式碼】中提到,因為有使用轉型(在巴士、機車類別中的 Accept
方法),會花費大量的執行時間,所以 Acyclic Visitor 模式不適用於硬即時模式(hard real-time system)。查了一下是什麼意思,將參考附圖於下方。
參考
VISITOR 模式.無瑕的程式碼 敏捷完整篇:物件導向原則、設計模式與 C# 實踐
16.即時作業系統