在 C# 中使用带有泛型的访问者模式

发布于 2024-07-13 12:31:45 字数 2063 浏览 7 评论 0原文

我想知道下面的访问者模式是否可以接受。 从 Accept() 或 Visit() 调用返回时我感觉有点不舒服 - 这是此模式的适当用法吗?如果不合适,为什么不呢?

注意:对长代码示例表示歉意,似乎有必要了解我正在做的事情,因为访问者似乎总是有点参与......

interface IAnimalElement<T>
{
   T Accept(IAnimalVisitor<T> visitor);
}

interface IAnimalVisitor<T>
{
    T Visit(Lion lion);
    T Visit(Peacock peacock);
    T VisitZoo(List<Animal> animals);
}

abstract class Animal
{
    public int Age { get; protected set; }
}

class Lion : Animal, IAnimalElement<int>
{
    public Lion(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class Peacock : Animal, IAnimalElement<int>
{
    public Peacock(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class AnimalAgeVisitor : IAnimalVisitor<int>
{
    public int TotalAge { get; private set; }

    int IAnimalVisitor<int>.Visit(Lion lion)
    {
        TotalAge += lion.Age;
        return lion.Age;
    }

    int IAnimalVisitor<int>.Visit(Peacock peacock)
    {
        TotalAge += peacock.Age + 10;
        return peacock.Age + 10; // peacocks ages are always -10y, correct.
    }

    public int VisitZoo(List<Animal> animals)
    {
        // Calculate average animal age.

        int sum = 0;
        int count = 0;
        foreach (IAnimalElement<int> animal in animals)
        {
            sum += animal.Accept(this);
            ++count;
        }

        return count == 0 ? 0 : sum / count;
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Animal> animals = new List<Animal>() { new Lion(10), 
          new Lion(15), new Peacock(3), new Lion(2), new Peacock(9) };

        AnimalAgeVisitor visitor = new AnimalAgeVisitor();

        Console.WriteLine("Average age = {0}, Total age = {1}", 
            visitor.VisitZoo(animals), visitor.TotalAge);
    }
}

I want to know whether the below is an acceptable use of the visitor pattern. I feel a little uncomfortable returning from an Accept() or Visit() call - is this an appropriate usage of this pattern and if not, why not?

Note: Apologies for the long code sample, seems necessary to get across what I'm doing as visitor always seems to be a little involved...

interface IAnimalElement<T>
{
   T Accept(IAnimalVisitor<T> visitor);
}

interface IAnimalVisitor<T>
{
    T Visit(Lion lion);
    T Visit(Peacock peacock);
    T VisitZoo(List<Animal> animals);
}

abstract class Animal
{
    public int Age { get; protected set; }
}

class Lion : Animal, IAnimalElement<int>
{
    public Lion(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class Peacock : Animal, IAnimalElement<int>
{
    public Peacock(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class AnimalAgeVisitor : IAnimalVisitor<int>
{
    public int TotalAge { get; private set; }

    int IAnimalVisitor<int>.Visit(Lion lion)
    {
        TotalAge += lion.Age;
        return lion.Age;
    }

    int IAnimalVisitor<int>.Visit(Peacock peacock)
    {
        TotalAge += peacock.Age + 10;
        return peacock.Age + 10; // peacocks ages are always -10y, correct.
    }

    public int VisitZoo(List<Animal> animals)
    {
        // Calculate average animal age.

        int sum = 0;
        int count = 0;
        foreach (IAnimalElement<int> animal in animals)
        {
            sum += animal.Accept(this);
            ++count;
        }

        return count == 0 ? 0 : sum / count;
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Animal> animals = new List<Animal>() { new Lion(10), 
          new Lion(15), new Peacock(3), new Lion(2), new Peacock(9) };

        AnimalAgeVisitor visitor = new AnimalAgeVisitor();

        Console.WriteLine("Average age = {0}, Total age = {1}", 
            visitor.VisitZoo(animals), visitor.TotalAge);
    }
}

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

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

发布评论

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

评论(4

小鸟爱天空丶 2024-07-20 12:31:45

对我来说,这感觉实施有点犹豫。

让您的 Visit 和 Accept 方法返回 void 并跟踪 Visitor 对象中的所有状态。 最后询问一下。

或者...

让访问和接受返回正在进行的状态并以功能方式接受传入的正在进行的状态。

如果您选择第二个选项,我不太确定是否需要访问者对象或模式,您可以使用迭代器、函数和一些瞬态状态。

Well to me this feels like the implementation is a little bit on the fence.

Either have your Visit and Accept methods return void and track all the state in the Visitor object. Interrogate it at the end.

or ...

Have Visit and Accept return an in-progress state and accept an incoming in-progress state in a functional manner.

If you go for the second option I'm not really sure that a visitor object or pattern is needed, you can use an iterator, function and some transient state instead.

む无字情书 2024-07-20 12:31:45

简短回答:我没有发现公开 IVisitor 返回通用参数有任何问题。
请参阅 FxCop 规则

然后它允许使用不同的IVisitor,每个都返回不同的值。

但是,就您的情况而言,Visitor 没有用,因为每种动物都有 Age 属性,因此所有操作都可以使用 Animal 或新的 <强>IAnimal界面。

另一种方法是使用多重调度,但代价是丢失强类型

当您想要替换(或避免编写) 像这样的开关

IAnimal animal = ...;
switch (animal.GetType().Name)
{
  case "Peacock":
    var peacock = animal as Peacock;
    // Do something using the specific methods/properties of Peacock
    break;
  case "Lion":
    var peacock = animal as Lion;
    // Do something using the specific methods/properties of Lion
    break;
   etc...
}

或者嵌套的if-then-else等价物。

其目的是通过使用多态性将实例路由到与其类型相关的例程,然后避免丑陋的 if-then-else/switch 语句手动转换。 此外,它还有助于减少不相关代码之间的耦合。

替代方法是在类树中添加一个虚拟方法来访问。 然而,有时这是不可能或不可取的:

  • 可访问类代码不可修改(例如不拥有)
  • 可访问类代码与访问代码无关(将其添加到类中意味着降低班级的凝聚力)。

这就是为什么它经常被用来遍历对象树(html 节点、词法分析器标记等)。 Visitor 模式意味着以下接口:

  • IVisitor

    /// <摘要> 
      /// 为访问其他类的类实现的接口。  
      /// 有关更多详细信息,请参阅访问者设计模式。 
      ///  
      /// 访问的类型。 
      /// 结果的类型。 
      公共接口 IVisitor   : IVisitor 电视访问过的地方 : IVisitable 
      { 
          TResult 访问(TVisited 已访问); 
      } 
    
      /// <摘要> 
      /// 标记接口。 
      ///  
      公共接口 IVisitor{} 
      
  • IVisitable

    /// <摘要> 
      /// 为访问者可访问的类实现的接口。 
      /// 有关更多详细信息,请参阅访问者设计模式。 
      ///  
      /// 访问者的类型。 
      /// 结果的类型。 
      公共接口 IVisitable   : IVisitable 所在 TVisitor : IVisitor 
      { 
          TResult Accept(TVisitor 访客); 
      } 
    
      /// <摘要> 
      /// 标记接口。 
      ///  
      公共接口 IVisitable {} 
      

每个IVisitableAccept 的实现应该调用Visit(this)

Short answer: I don't see any problems of exposing a IVisitor returning a generic parameter.
See FxCop rules.

It then permits to use different IVisitor each returning a different value.

However, in your case, Visitor is not useful, since every animal has the Age property so all can be done with Animal or a new IAnimal interface.

Alternative is using multiple-dispatch at the cost of losing Strong Typing.

Use a Visitor pattern when you want to replace (or avoid to write) a switch like this one:

IAnimal animal = ...;
switch (animal.GetType().Name)
{
  case "Peacock":
    var peacock = animal as Peacock;
    // Do something using the specific methods/properties of Peacock
    break;
  case "Lion":
    var peacock = animal as Lion;
    // Do something using the specific methods/properties of Lion
    break;
   etc...
}

or the nested if-then-else equivalent.

It's purpose is to route the instance to the routine relevant for its type by using polymorphism and then avoid ugly if-then-else/switch statements and manual casts. Furthermore, it helps to decrease coupling between unrelated code.

Alternative to that is to add a virtual method in the class tree to visit. However, sometimes it's not possible or desirable :

  • visitable class code not modifiable (not owned for example)
  • visitable class code not related to visiting code (adding it in class would mean lowering the cohesion of the class).

That's why it's often used to traverse an object tree (html nodes, lexer tokens, etc...). Visitor pattern implies the following interfaces:

  • IVisitor

    /// <summary>
    /// Interface to implement for classes visiting others. 
    /// See Visitor design pattern for more details.
    /// </summary>
    /// <typeparam name="TVisited">The type of the visited.</typeparam>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    public interface IVisitor<TVisited, TResult> : IVisitor where TVisited : IVisitable
    {
        TResult Visit(TVisited visited);
    }
    
    /// <summary>
    /// Marking interface.
    /// </summary>
    public interface IVisitor{}
    
  • IVisitable

    /// <summary>
    /// Interface to implement for classes visitable by a visitor.
    /// See Visitor design pattern for more details.
    /// </summary>
    /// <typeparam name="TVisitor">The type of the visitor.</typeparam>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    public interface IVisitable<TVisitor, TResult> : IVisitable where TVisitor : IVisitor
    {
        TResult Accept(TVisitor visitor);
    }
    
    /// <summary>
    /// Marking interface.
    /// </summary>
    public interface IVisitable {}
    

Implementation of Accept in each IVisitable should call Visit(this).

舟遥客 2024-07-20 12:31:45

可访问的接受方法不应返回任何内容。 接受仅应指示访问者在访问之后或访问期间要访问什么。

The visitable accept method is not supposed to return anything. The accept is only supposed to indicate the visitor what to visit after or during the visit.

水溶 2024-07-20 12:31:45

这很常见。 我不知道你是否可以在 C# 中做到这一点,但在 Java 中,通常将 Accept 方法保留为通用,因此返回的内容由访问者而不是访问者决定:

interface IAnimalElement
{
   <T> T Accept(IAnimalVisitor<T> visitor);
}


interface IAnimalVisitor<T> 
{
   T Visit(Peacock animal);
  ...
}

对于过程,一个 IAnimalVisitorIAnimalVisitornull 的 code> 。

It's fairly common. I don't know if you can do it in C#, but in Java it's normal to leave the Accept method generic, so what's returned is decided by the visitor not the visitee:

interface IAnimalElement
{
   <T> T Accept(IAnimalVisitor<T> visitor);
}


interface IAnimalVisitor<T> 
{
   T Visit(Peacock animal);
  ...
}

For procedures, a IAnimalVisitor<Void> returning null can be used.

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