繁体   English   中英

C# 中的用户可扩展访问者模式

[英]User extendable visitor pattern in C#

是否可以在 C# 中创建用户可扩展的访问者模式? (最好是 .net 3.5)

我希望在访问者模式中添加功能的库中有一组类。 问题是库的用户也可以创建自己的类。 这意味着您需要创建一个特殊的访问者来接受新的 class 类型,但我们的 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);
         }
      }
   }
}

好像你把一切都搞反了。 首先,我们来谈谈里氏替换原则。 它说任何类型都应该可以被基本类型替换。 这也适用于访问者模式。

如果您有一个名为void Accept(IVisitor visitor)的方法,那么访问的FancyVisitorSipleVisitor应该没有关系。

访问者模式的整个想法是,主题(即正在访问的 class)不应该比它实现的合同(基本 class 或接口)更了解访问者。 并且每个Visitor class 应该特定于被访问的某个 class。

这就是您的代码的问题。 您正在尝试制作一个可以访问所有系统组件的通用访问者 class。 那是完全错误的。

在我看来,您有两个选择:

您希望从所有系统组件中收集相同类型的信息。

简单的。 创建一个所有系统组件都实现的新接口。 然后将访问者更改为Visit(ISystemCompoent subject)

您想从每个系统组件中收集不同类型的信息

然后您需要创建不同的访问者基类(或接口)。

不,不可能将访问者模式与可扩展 class 层次结构的愿景混合在一起。 它们是相互排斥的。

这一系列博客文章中的一个解决方案可能涉及使用“ 接口和动态类型转换来克服访问者模式的可扩展 class 层次结构问题

例如:

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

(不是说这是最好的,只是另一种选择)

是的,您可以使用反射来做到这一点。 使用访问者模式的主要思想是双重调度。 使用反射,您可以从任何访问者那里获取所有Visit(...)方法,并根据 Visit 方法的参数类型调用正确的方法。

如果您使用 go 这条路线,则您不一定需要访问者或您正在访问的元素的 inheritance 层次结构。 事实上,元素类甚至不需要知道访问者界面(或基类)。

为了清楚起见,下面是一个代码示例,它实现了一个使用反射进行双重调度的通用访问者。 使用GenericVisitor<T>::AcceptVisitor(...) ,只要访问者T定义了一个方法Visit(...)对于特定元素 class。

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);
        }
    }
}

您可以像这样使用新的动态关键字:

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

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

但是,您将代码暴露给MissingMethodException

是的,你可以这样做。

  1. 将所有 UserThing 的Accept(SystemVisitor visitor)方法更改为接受UserVisitor

  2. 为您的所有 UserThing 添加一个抽象基础UserThing

  3. 在抽象基础 class 添加一个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可以。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM