2011-06-10 56 views
4

是否可以在C#中創建用戶可擴展訪問者模式? (最好是.net 3.5)C#中的用戶可擴展訪問者模式

我在庫中有一組類,我希望使用訪問者模式添加功能。問題是圖書館的用戶也有可能創建自己的課程。這意味着您需要創建一個特殊的訪問者來接受新的類類型,但是我們的Accept方法被設置爲接收基類型。我怎樣才能讓派生類在派生訪問者中調用正確的方法。

或者有沒有這樣做「,如果這種類型的,這樣做「的另一種方式

一些示例代碼:?

/* In library */ 
namespace VisitorPattern.System 
{ 
    interface IThing 
    { 
     void Accept(SystemVisitor visitor); 
     void ThingMethodA(...); 
     void ThingMethodB(...); 
    } 

    class SystemThingA : IThing 
    { 
     public void Accept(SystemVisitor visitor) { visitor.Visit(this); } 
     ...ThingMethods... 
    } 
    class SystemThingB : IThing 
    { 
     public void Accept(SystemVisitor visitor) { visitor.Visit(this); } 
     ...ThingMethods... 
    } 
    class SystemThingC : IThing 
    { 
     public void Accept(SystemVisitor visitor) { visitor.Visit(this); } 
     ...ThingMethods... 
    } 

    class SystemVisitor 
    { 
     public SystemVisitor(object specialSystemServices) { } 
     public virtual void Visit(SystemThingA thing) { Console.WriteLine("SystemThingA"); } 
     public virtual void Visit(SystemThingB thing) { Console.WriteLine("SystemThingB"); } 
     public virtual void Visit(SystemThingC thing) { Console.WriteLine("SystemThingC"); } 
     public virtual void Visit(IThing thing) { Console.WriteLine("sysvis:IThing"); } 
    } 
} 

/* in user code */ 
namespace VisitorPattern.User 
{ 
    using VisitorPattern.System; 

    class UserThingA : IThing 
    { 
     public void Accept(SystemVisitor visitor) 
     { 
     var userVisitor = visitor as UserVisitor; 
     if (userVisitor == null) throw new ArgumentException("visitor"); 
     userVisitor.Visit(this); 
     } 
     ...ThingMethods... 
    } 
    class UserThingB : IThing 
    { 
     public void Accept(SystemVisitor visitor) 
     { 
     var userVisitor = visitor as UserVisitor; 
     if (userVisitor == null) throw new ArgumentException("visitor"); 
     userVisitor.Visit(this); 
     } 
     ...ThingMethods... 
    } 
    class UserThingC : IThing 
    { 
     public void Accept(SystemVisitor visitor) 
     { 
     var userVisitor = visitor as UserVisitor; 
     if (userVisitor == null) throw new ArgumentException("visitor"); 
     userVisitor.Visit(this); 
     } 
     ...ThingMethods... 
    } 

    // ????? 
    class UserVisitor : SystemVisitor 
    { 
     public UserVisitor(object specialSystemServices, object specialUserServices) : base(specialSystemServices) { } 

     public void Visit(UserThingA thing) { Console.WriteLine("UserThingA"); } 
     public void Visit(UserThingB thing) { Console.WriteLine("UserThingB"); } 
     public void Visit(UserThingC thing) { Console.WriteLine("UserThingC"); } 
     public override void Visit(IThing thing) { Console.WriteLine("uservis:IThing"); } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
     var visitor = new UserVisitor("systemservice", "userservice"); 
     List<IThing> mylist = new List<IThing> { new UserThingA(), new SystemThingB(), new SystemThingC(), new UserThingC() }; 
     foreach (var thing in mylist) 
     { 
      thing.Accept(visitor); 
     } 
     } 
    } 
} 
+0

你能修改'SystemVisitor.Visit(SystemThingA東西)''SystemVisitor.Visit(IThingthing)'嗎? – Norbert 2011-06-10 06:14:25

+0

好點。那麼它編譯,但我不知道如何讓訪問對象在訪問者中調用正確的方法。 – 2011-06-10 06:24:05

回答

2

不,不可能將訪問者模式與可擴展類層次結構的願景混合在一起。它們是相互排斥的。

+0

感謝您的回答,您有任何建議的替代解決方案嗎? – 2011-06-12 04:31:17

3

好像你得到這一切的一切倒退首先,讓我們來談談Liskov替代原則,它表示任何類型都應該可以被基類型替換,這也適用於訪問者模式

如果您有一種稱爲void Accept(IVisitor visitor)的方法,那麼FancyVisitorSipleVisitor正在訪問。

與訪問者模式的整個想法是主題(即正在訪問的類)不應該知道任何有關訪問者比它實現的契約(基類或接口)更多。每個Visitor類都應該針對某個正在訪問的類。

這就是你的代碼的問題。您正試圖製作一個可訪問所有系統組件的常規訪問者類。這顯然是錯誤的。

在我看來,你有兩個選擇:

你想從所有系統組件收集相同類型的信息。

簡單。創建一個所有系統組件實現的新界面。然後將訪問者更改爲Visit(ISystemCompoent subject)

你想從每個系統組件

然後,你需要創建不同訪問者基類(或接口)收集不同類型的信息。

+0

我相信訪問者模式的重點在於訪問者對層次結構中的每個類都有特定的方法。通過訪問類的Accept方法,通過雙重調度,正確的方法被稱爲 。 我不確定,但您的建議聽起來更像是使用 無雙重調度的策略模式。 由於傳統上訪問者需要了解層次結構中的每個類,因此外部庫用戶很難爲層次結構添加新的類,並修改訪問者接口。 – 2011-06-12 04:40:11

1

是的,你可以這樣做使用反射。使用訪客模式的主要想法是雙派遣。使用反射,您可以從任何訪客獲取所有Visit(...)方法,並根據Visit方法的參數類型調用正確的方法。

如果你走這條路線,你不一定需要訪問者或你訪問的元素的繼承層次結構。事實上,元素類甚至不需要知道訪問者接口(或基類)。

爲了說清楚,下面是一個代碼示例,它實現了使用反射來執行雙重調度的通用訪問者。使用GenericVisitor<T>::AcceptVisitor(...),只要訪問者T爲該特定元素類定義了方法Visit(...),就可以獲取任何元素(派生或不派生)在任何訪問者中調用正確的方法(派生或不派生)。

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Reflection; 

namespace VisitorPattern 
{ 
    class GenericVisitor<T> 
    { 
     // Dictionary whose key is the parameter type and value is the MethodInfo for method "Visit(ParameterType)" 
     static Dictionary<Type, MethodInfo> s_visitorMethodDict; 
     static GenericVisitor() 
     { 
      s_visitorMethodDict = new Dictionary<Type, MethodInfo>(); 

      Type visitorType = typeof(T); 
      MethodInfo[] visitorMethods = visitorType.GetMethods(); 

      // Loop through all the methods in class T with the name "Visit". 
      foreach (MethodInfo mi in visitorMethods) 
      { 
       if (mi.Name != "Visit") 
        continue; 

       // Ignore methods with parameters > 1. 
       ParameterInfo[] parameters = mi.GetParameters(); 
       if (parameters.Length != 1) 
        continue; 

       // Store the method in the dictionary with the parameter type as the key. 
       ParameterInfo pi = parameters[0]; 
       if (!s_visitorMethodDict.ContainsKey(pi.ParameterType)) 
        s_visitorMethodDict.Add(pi.ParameterType, mi); 
      } 
     } 

     public static bool AcceptVisitor(object element, T visitor) 
     { 
      if (element == null || visitor == null) 
       return false; 

      Type elementType = element.GetType(); 

      if (!s_visitorMethodDict.ContainsKey(elementType)) 
       return false; 

      // Get the "Visit" method on the visitor that takes parameter of the elementType 
      MethodInfo mi = s_visitorMethodDict[elementType]; 

      // Dispatch! 
      mi.Invoke(visitor, new object[] { element }); 

      return true; 
     } 
    } 

    // Element classes (note: they don't necessarily have to be derived from a base class.) 
    class A { } 
    class B { } 

    class Visitor 
    { 
     public void Visit(A a) { System.Console.WriteLine("Visitor: Visited A"); } 
     public void Visit(B b) { System.Console.WriteLine("Visitor: Visited B"); } 
    } 

    interface IVisitor 
    { 
     void Visit(A a); 
     void Visit(B b); 
    } 

    class DerivedVisitor : IVisitor 
    { 
     public void Visit(A a) { System.Console.WriteLine("DerivedVisitor: Visited A"); } 
     public void Visit(B b) { System.Console.WriteLine("DerivedVisitor: Visited B"); } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Object a = new A(); 
      Object b = new B(); 

      // Example of Visitor that doesn't use inheritance. 
      Visitor v1 = new Visitor(); 
      GenericVisitor<Visitor>.AcceptVisitor(a, v1); 
      GenericVisitor<Visitor>.AcceptVisitor(b, v1); 

      // Example of Visitor that uses inheritance. 
      IVisitor v2 = new DerivedVisitor(); 
      GenericVisitor<IVisitor>.AcceptVisitor(a, v2); 
      GenericVisitor<IVisitor>.AcceptVisitor(b, v2); 
     } 
    } 
} 
+0

你的例子對我毫無意義。 * derived * visitor應該是** interface **,它擴展了'IVisitor'併爲添加到對象圖中的新類類型添加了新方法。你所有的無數訪問者都會公開同樣的方法,這些方法根本不能解決OP的問題。此外,您已將訪問者實現從類層次結構中分離出來 - 語法應爲'a.Accept(v1)'而不是'GenericVisitor .AcceptVisitor(a,v1)' - 這違背了主要優點之一該模式。 – 2011-07-22 23:57:42

+0

爲了具體回答Nick在他的例子中如何使用它,他必須在主函數中將他的for循環從'thing.Accept(visitor);'改爲'GenericVisitor .AcceptVisitor(thing,visitor);'。我同意這不是傳統的訪問者模式,但它仍然給你雙重調度的能力,這是訪問者模式的本質。 } – Vivek 2011-07-23 21:56:15

0

你可以使用新的動態關鍵字如下:

public class Visitable1 
{ 
    public void Accept(dynamic visitor) 
    { 
     visitor.Visit(this); 
    } 
} 

public class DynamicVisitor 
{ 
    public void Visit(Visitable1 token) 
    { 
     // Call token methods/properties 
    } 
} 

但是,你暴露你的代碼MissingMethodException

0

是的,你可以做到這一點。

  1. 更改所有UserThing的Accept(SystemVisitor visitor)方法接受UserVisitor代替。

  2. 添加一個抽象基類爲所有UserThing小號

  3. 在抽象基類中添加Accept方法試圖從SystemVisitor投訪問者到UserVisitor。如果成功,則調用UserThing上的Accept方法。

    public override void Accept(SystemVisitor visitor) 
    { 
        var visitorAsUser = visitor as UserVisitor; 
        if (visitorAsUser != null) 
         return this.Accept(UserVisitor); 
    } 
    

SystemVisitor仍然知道你UserThing沒什麼和現有SystemVisitor不能訪問他們,但你UserVisitor即可。