Расширяемый пользователем шаблон посетителя в C # - PullRequest
4 голосов
/ 10 июня 2011

Можно ли создать расширяемый пользователем шаблон посетителя в 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);
         }
      }
   }
}

Ответы [ 6 ]

3 голосов
/ 10 июня 2011

Похоже, вы все поняли задом наперед.Прежде всего, поговорим о принципе подстановки Лискова.Это говорит о том, что любой тип должен быть заменен базовым типом.Это также относится к шаблону посетителя.

Если у вас есть метод с именем void Accept(IVisitor visitor), не должно иметь значения, посещает ли FancyVisitor или SipleVisitor.

ВесьИдея с шаблоном посетителя состоит в том, что субъект (т. е. класс, который посещается) не должен знать ничего о посетителе больше, чем контракт (базовый класс или интерфейс), который он реализует.И каждый Visitor класс должен быть определенным для определенного посещаемого класса.

И это проблема с вашим кодом.Вы пытаетесь создать общий класс Visitor, который может посещать все компоненты вашей системы.Это совершенно неправильно.

На мой взгляд, у вас есть два варианта:

Вы хотите собирать одинаковую информацию от всех компонентов системы.

Easy.Создайте новый интерфейс, который реализуют все компоненты системы.Затем измените посетителя на Visit(ISystemCompoent subject).

. Вы хотите собирать различную информацию от каждого компонента системы

Затем вам нужно создать различных * 1029.* базовые классы посетителей (или интерфейсы).

2 голосов
/ 10 июня 2011

Нет, невозможно смешать шаблон посетителя с видениями расширяемой иерархии классов.Они взаимоисключающие.

1 голос
/ 23 июля 2011

Да, вы можете сделать это с помощью отражения.Основная идея использования шаблона посетителя для двойной отправки.Используя рефлексию, вы можете получить все методы Visit(...) от любого посетителя и вызвать правильный метод, основанный на типе параметра метода Visit.

Если вы пойдете по этому пути, вам не обязательно понадобится иерархия наследования для посетителя или элемента, который вы посещаете.Фактически, классы элементов даже не должны знать об интерфейсе посетителя (или базовом классе).

Чтобы прояснить ситуацию, ниже приведен пример кода, который реализует универсальный посетитель, который использует отражение для выполнения doubleотправка.Используя 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);
        }
    }
}
1 голос
/ 10 июня 2011

Одно решение из этой серии сообщений в блоге может включать использование " интерфейсов и динамических приведений типов для преодоления проблем шаблона посетителя с расширяемыми иерархиями классов "

, например:

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

(Не говоря, что это лучшее, просто альтернатива)

0 голосов
/ 04 апреля 2016

Да, вы можете сделать это.

  1. Измените все методы Accept(SystemVisitor visitor) вашего UserThing, чтобы вместо них принять UserVisitor.

  2. Добавьте абстрактный базовый класс для всех ваших UserThing s

  3. В абстрактный базовый класс добавьте метод Accept, который пытается преобразовать посетителя из SystemVisitor в UserVisitor. в случае успеха вызывает метод Accept для UserThing.

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

SystemVisitor до сих пор ничего не знает о ваших UserThing s, и существующие SystemVisitor не могут их посетить, но ваша UserVisitor может.

0 голосов
/ 23 декабря 2015

Вы можете использовать новое ключевое слово 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

...