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

发布于 2024-11-14 19:50:16 字数 3148 浏览 4 评论 0原文

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

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

Is it possible to create a user extendable visitor pattern in C#? (preferably .net 3.5)

I have a set of classes in a library that I wish to add functionality to with the visitor pattern. The problem is that it is also possible for the user of the library to create their own classes. This means that you need to create a special visitor that will accept the new class types but our Accept methods are setup to receive the base type. How can I get the derived classes to call the right method in the derived visitor.

Or is there another way of doing 'if this type, do this"?

Some example code:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

上课铃就是安魂曲 2024-11-21 19:50:16

看来你把一切都搞反了。首先我们先来说一下里氏替换原理。它说任何类型都应该可以被基本类型替换。这也适用于访客模式。

如果您有一个名为 void Accept(IVisitor Visitor) 的方法,那么访问的是 FancyVisitor 还是 SipleVisitor 都没有关系。

访问者模式的整体思想是,主体(即被访问的类)不应该比访问者所实现的契约(基类或接口)了解更多关于访问者的信息。每个 Visitor 类应该特定于被访问的某个类。

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

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

您想要从所有系统组件收集相同类型的信息。

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

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

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

Seems like you got it all backwards. First of all, let's talk about the Liskov Substitution Principle. It says that any type should be replaceable by the base type. This also apply to the visitor pattern.

If you have a method called void Accept(IVisitor visitor), it should not matter if a FancyVisitor or a SipleVisitor that is visiting.

The whole idea with the visitor pattern is that the subject (i.e. the class that is being visited) should not know anything about the visitor more than the contract (base class or interface) that it implements. And each Visitor class should be specific for a certain class being visited.

And that's the problem with your code. You are trying to make a general Visitor class that can visit all your system components. That's plain wrong.

As I see it, you have two options:

You want to collect the same kind of information from all system components.

Easy. Create a new interface which all system components implement. Then change the visitor to Visit(ISystemCompoent subject).

You want to collect different kinds of information from each system component

Then you need to create different visitor base classes (or interfaces).

蒗幽 2024-11-21 19:50:16

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

No, it is not possible to mix the visitor pattern with visions of an extensible class hierarchy. They are mutually exclusive.

少钕鈤記 2024-11-21 19:50:16

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

例如:(

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

并不是说这是最好的,只是一种替代方案)

One solution from this series of blog posts could involve using "interfaces and dynamic type casts to overcome the Visitor pattern’s problems with extensible class hierarchies"

eg:

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

(Not saying this is the best, just an alternative)

国粹 2024-11-21 19:50:16

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

如果您选择这条路线,则不一定需要访问者或您正在访问的元素的继承层次结构。事实上,元素类甚至不需要了解访问者接口(或基类)。

为了清楚起见,下面是一个代码示例,它实现了使用反射进行双重调度的通用访问者。使用 GenericVisitor::AcceptVisitor(...),您可以获取任何元素(无论是否派生)来调用任何访问者(无论是否派生)中的正确方法,只要Visitor 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);
        }
    }
}

Yes, you can do this using reflection. The main idea of using a visitor pattern is for double dispatch. Using reflection you can get all the Visit(...) methods from any visitor and invoke the right method based on the parameter type of the Visit method.

If you go this route, you won't necessarily need an inheritance hierarchy for the visitor or the element you are visiting. In fact, the element classes won't even need to know about the visitor interface (or base class).

To make it clear, below is a code sample that implements a generic visitor that uses reflection to do double dispatch. Using the GenericVisitor<T>::AcceptVisitor(...), you can get any element (derived or not) to call the right method in the any visitor (derived or not) as long as the visitor T defines a method Visit(...) for that particular element 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);
        }
    }
}
清晨说晚安 2024-11-21 19:50:16

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

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

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

但是,您将代码暴露给MissingMethodException

You could use the new dynamic keyword like this:

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

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

However, you expose your code to MissingMethodException

寄离 2024-11-21 19:50:16

是的,你可以这样做。

  1. 更改所有 UserThing 的 Accept(SystemVisitor guest) 方法以接受 UserVisitor

  2. 为所有 UserThing 添加一个抽象基类

  3. 在抽象基类中添加Accept 方法尝试将访问者从 SystemVisitor 转换为 UserVisitor。如果成功,它将调用 UserThing 上的 Accept 方法。

    public override void Accept(SystemVisitor 访客)
    {
        var VisitorAsUser = 访客作为 UserVisitor;
        if (visitorAsUser!= null)
            返回 this.Accept(UserVisitor);
    }
    

SystemVisitor 仍然对您的 UserThing 一无所知,并且现有的 SystemVisitor 无法访问它们,但您的 UserVisitor 可以。

Yes, you can do this.

  1. Change all your UserThing's Accept(SystemVisitor visitor)methods to accept a UserVisitor instead.

  2. Add an abstract base class for all your UserThings

  3. In the abstract base class add an Accept method that attempts to cast the visitor from a SystemVisitor to a UserVisitor. if it succeeds it calls the Accept method on the UserThing.

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

The SystemVisitor still knows nothing about your UserThings and an existing SystemVisitor cannot visit them, but your UserVisitor can.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文