有時會需要對既有的類別新增方法,但該方法又會因為不同的類別有些微的差異,最土炮的方法是在各個類別中實作該方法,但如果下次又有類似的需求,又要再次修改各個類別,這樣無法遵守開放封閉原則。
這時可以使用訪問者模式,在不改變既有類別的情況下,將欲新增的方法收攏至訪問者類別中,【無瑕的程式碼】書中介紹的訪問者種類有以下四種,我會各自發一篇文做介紹:
- Visitor 模式
- Acyclic Visitor 模式
- Decorator 模式
- Extenstion Object 模式
Visitor 模式
現在有兩種交通工具;機車和巴士,假設乘客只在意三個項目:馬力、限乘人數、有沒有冷氣.將這三個屬性定義在介面,另外還需定義一個方法 Accept,使機車和巴士類別能夠讓 Visitor 訪問。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
namespace DemoCode.DesignPattern.Visitor.OriginVisitor { public interface IVisitor { void Visit(Motor motor);
void Visit(Bus bus); } }
|
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.OriginVisitor { public interface ITransportation { decimal Power { get; set; }
int NumberOfPassenger { get; set; }
bool HasAirConditioner { get; set; }
void Accept(IVisitor 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
|
namespace DemoCode.DesignPattern.Visitor.OriginVisitor { public class Motor : ITransportation { public decimal Power { get; set; }
public int NumberOfPassenger { get; set; }
public bool HasAirConditioner { get; set; }
public Motor(decimal power, int numberOfPassenger, bool hasAirConditioner) { this.Power = power; this.NumberOfPassenger = numberOfPassenger; this.HasAirConditioner = hasAirConditioner; }
public void Accept(IVisitor visitor) { visitor.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
|
namespace DemoCode.DesignPattern.Visitor.OriginVisitor { public class Bus : ITransportation { public decimal Power { get; set; }
public int NumberOfPassenger { get; set; } public bool HasAirConditioner { get; set; }
public Bus(decimal power, int numberOfPassenger, bool hasAirConditioner) { this.Power = power; this.NumberOfPassenger = numberOfPassenger; this.HasAirConditioner = hasAirConditioner; }
public void Accept(IVisitor visitor) { visitor.Visit(this); } } }
|
實作 Visitor 類別
我們使用名為雙重分發 (dual dispatch) 的技術,這項技術是 Visitor 模式的核心機制,這個雙重分發涉及了兩個多型分發,第一個是機車及巴士類別的 Accept 方法,我們可以根據該分發辨別出是機車還是巴士類別呼叫了 Accept 方法;第二個是 Accept 方法所呼叫的 Visit 方法,根據上一個多型分發,判斷出是哪一個類別,再呼叫該類別所要執行的特定函式。
接著我們根據不同的類別,實作不同的 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 32 33
|
using System;
namespace DemoCode.DesignPattern.Visitor.OriginVisitor { public class Visitor : IVisitor { public void Visit(Motor motor) { Console.WriteLine($"這是台機車,我只在意馬力:{motor.Power}"); }
public void Visit(Bus 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 Bus(1000, 20, true); var motor = new Motor(100, 2, false);
var visitor = new Visitor(); bus.Accept(visitor); motor.Accept(visitor); } }
|
最後輸出結果如下:
1 2
| 我是台公車,我只在意限乘人數:20,以及有沒有冷氣:有冷氣 我是台機車,我只在意馬力:100
|
完整程式碼請參考
結論
Visitor 模式讓我們在不修改既有類別的情形下,替巴士及機車類別新增了方法。
維基百科說道:訪問者模式是一種將算法與對象結構分離的軟體設計模式。若類別不常異動 (e.g. 新增一個飛機類別),但常常需要新增方法,則可以使用 Visitor 模式。
參考
VISITOR 模式.無瑕的程式碼 敏捷完整篇:物件導向原則、設計模式與 C# 實踐
Wiki - 訪問者模式
每個人關心的點都不同 - 訪問者模式 (Visitor Pattern)
訪問者模式 (Visitor Pattern)