【问题标题】:Is this design using dynamic okay?这个设计使用动态好吗?
【发布时间】:2014-02-18 16:35:17
【问题描述】:

我的申请中有一个要处理的不同工作的列表。我正在玩弄一种使用不同类型来代表不同类型工作的设计——这是很自然的,因为它们具有不同的属性等等。对于处理,我正在考虑按照下面的代码在 C# 中使用动态关键字。

abstract class Animal {}    
class Cat : Animal {} 
class Dog : Animal {}

class AnimalProcessor
{
    public void Process(Cat cat)
    {
        System.Diagnostics.Debug.WriteLine("Do Cat thing");
    }

    public void Process(Dog dog)
    {
        System.Diagnostics.Debug.WriteLine("Do Dog thing");
    }

    public void Process(Animal animal)
    {
        throw new NotSupportedException(String.Format("'{0}' is type '{1}' which isn't supported.",
            animal,
            animal.GetType()));
    }
}

internal class Program
{
    private static void Main(string[] args)
    {
        List<Animal> animals = new List<Animal>
        {
            new Cat(),
            new Cat(),
            new Dog(),
            new Cat()
        };

        AnimalProcessor animalProcessor = new AnimalProcessor();
        foreach (dynamic animal in animals)
        {
            animalProcessor.Process(animal);
        }

        //Do stuff all Animals need.
    }
}

代码按预期工作,但是,我有一种挥之不去的感觉,我错过了一些非常明显的东西,并且有一个更好(或更广为人知)的模式可以做到这一点。

有没有更好或同等好但更被接受的模式来处理我的动物?或者,这样好吗?并且,请解释为什么任何替代方案都更好。

【问题讨论】:

  • 如果您可以控制所有相关类的源代码,dynamic 在这里将是一个矫枉过正。只需将 Animal.Process 设为 abstract 方法并在派生类中覆盖它。

标签: c# design-patterns dynamic polymorphism dispatch


【解决方案1】:

您不会错过任何东西:在对象的运行时类型上调度是 dynamic 可以做得很好的事情。

您确实有其他选择,例如实现 visitor pattern,但它的实现成本要高得多,而且它的可读性也不如您使用 dynamic 的方法:

interface IAnimalProcessor { // The visitor interface
    void Process(Cat cat);
    void Process(Dog dog);
    void Process(Animal animal);
}
class AnimalProcessor : IAnimalProcessor {
    ...
}
interface IProcessable {
    void Accept(IAnimalProcessor proc);
}
class Cat : IProcessable {
    public void Accept(IAnimalProcessor proc) {
        proc.Process(this); // Calls the Cat overload
    }
}
class Dog : IProcessable {
    public void Accept(IAnimalProcessor proc) {
        proc.Process(this); // Calls the Dog overload
    }
}
...
AnimalProcessor animalProcessor = new AnimalProcessor();
foreach (IProcessable animal in animals) {
    animal.Accept(animalProcessor); // The animal will call back the right method of the proc
}

【讨论】:

  • 啊,是的,这很聪明。我已经阅读了有关访问者模式的信息,但直到现在它还没有被“点击”。谢谢。
【解决方案2】:

这绝对是糟糕的设计。
你根本无法为 AnimalProcessor 引入接口, 因为添加新的 Animal 子类会导致接口发生变化。
此外,AnimalProcessor 的客户端应该知道用于类使用的特殊构造(foreach with dynamic)。 如果您仍想在这种情况下使用“动态”,请考虑以下内容:

class AnimalProcessor
{
    public void Process(Animal animal)
    {
        dynamic concreteAnimal = animal;

        DoProcess(concreteAnimal);
    }

    private void DoProcess(Animal animal)
    {
        throw new NotImplementedException();
    }

    private void DoProcess(Cat cat)
    {
        System.Diagnostics.Debug.WriteLine("Do Cat thing");
    }

    private void DoProcess(Dog dog)
    {
        System.Diagnostics.Debug.WriteLine("Do Dog thing");
    }
}

至少,您将来可以提取接口并拥有AnimalProcessor的不同实现。 而且你仍然会遇到一个严重的问题:这绝对不明显,在添加子类之后你需要向某个独立类添加方法,否则你的系统会抛出。

【讨论】:

  • +1。是的,我喜欢你关于界面的观点。使用一种方法(即 void Process(Animal animal))将“调度”的实现隐藏在接口后面可以解决这个问题。但是,处理每种不同动物类型的实际行为可以使用访问者模式或使用动态来实现。
  • 对于我来说,我不喜欢这种情况下的访问者模式,我更喜欢责任链。但是最简单的 map 仍然是 imo 的最佳选择。检查相关链接,如果你有兴趣:stackoverflow.com/questions/14613971/…, stackoverflow.com/questions/1477471/…
  • 谢谢。责任链很有趣。我特别喜欢您的第二个链接(带有消息处理的示例)。
  • There is an implementation of Visitor pattern using dynamic 关键字,我编写了与@FireAlkazar 回答您的问题相同的代码。最好使用所有好的 C# 功能,而不是坚持老式的 GoF 实现。
【解决方案3】:

您可能想要做的不是使用动态类型而是使用您的基本类型。 这样效果会更好。

在您的情况下,因为您正在调用基本流程方法。这是基本的Polymorphism

foreach (Animal animal in animals)
{
    animal.Process();
}

【讨论】:

  • +1,您可能需要添加 Animal.process 必须是 virtual 或更好,abstract
  • 是的,同意,因为您正在抛出 NotSupportedException,所以最好将其抽象化。
  • 是的,谢谢,但在这种情况下,我不想将实现放在子类型本身中。
猜你喜欢
  • 1970-01-01
  • 2012-02-10
  • 1970-01-01
  • 2011-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-26
相关资源
最近更新 更多