English 中文(简体)
在C#中使用泛型的访问者模式
原标题:
  • 时间:2009-02-03 18:30:19
  •  标签:

我想知道下面是否是“访问者模式”的可接受用法。我感觉从 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);
    }
}
最佳回答

对我来说,这感觉像是实施的立场有点踌躇不前。

要么让您的Visit和Accept方法返回void,并在Visitor对象中跟踪所有状态。在最后进行查询。

或者...

以功能化的方式让“访问”和“接受返回”返回正在进行中的状态,并接受正在进行中的输入状态。

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

问题回答

简短回答:我不认为公开展示返回通用参数的IVisitor会有任何问题。请参阅FxCop规则。

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

然而,在你的情况下,访客并不是有用的,因为每个动物都有年龄的属性,所以一切可以与动物或新的IAnimal接口完成。

另一种选择是使用多重分派,但会牺牲强类型化。

当你想要替换(或避免编写)像这样的 switch 时,请使用访问者模式

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语句和手动转换。此外,它有助于减少不相关代码之间的耦合。

另一个选择是在类树中添加一个虚拟方法进行访问。但是,有时候这是不可能或不理想的:

  • 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).

这就是为什么它通常用于遍历对象树(例如html节点、词法分析器标记等)。访问者模式意味着以下接口:

  • 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{}
    
  • 可访问的 (Kě fǎngwèn de)

    /// <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 可访问的 (Kě fǎngwèn de) should call Visit(this).

可访问的accept方法不应该返回任何东西。accept只是应该指示访问者在访问期间或之后要访问什么。

这是相当普遍的。我不知道你能否在C#中这样做,但在Java中,常规做法是让Accept方法保持通用性,所以返回的内容取决于访问者而非被访问者。

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


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

对于程序,可以使用返回nullIAnimalVisitor<Void>





相关问题
热门标签