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

时间:2009-02-03 18:30:19

标签: c# design-patterns visitor

我想知道以下是否是对访客模式的可接受使用。从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);
    }
}

4 个答案:

答案 0 :(得分:4)

对我而言,这感觉就像实施有点障碍。

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

或......

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

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

答案 1 :(得分:3)

简答:我没有看到任何暴露IVisitor返回通用参数的问题。
请参阅FxCop rules

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

但是,在您的情况下,访问者无用,因为每只动物都有年龄属性,因此所有动物都可以使用动物或一个新的 IAnimal 界面。

替代方案是使用 multiple-dispatch ,代价是失去强类型

如果您想要替换(或避免写入)开关,请使用Visitor pattern

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节点,词法分析器等等)。 访问者模式意味着以下界面:

  • <强> 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 {}
    

在每个 IVisitable 中实施接受应致电访问(此)

答案 2 :(得分:1)

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

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


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

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

答案 3 :(得分:1)

可访问的accept方法不应返回任何内容。接受只应表明访客在访问后或访问期间要访问的内容。