4

显然试图在这里简化问题。我有一个基类和一些派生类:

public class Mammal { }

public class Cat : Mammal { } 

public class Dog : Mammal { }

还有一个实用程序类:

public static class AnotherClass
{
    public static void GiveFood(Cat cat) {}
    public static void GiveFood(Dog dog) {}
}

在其他地方是一个方法,Feed,它接受一个哺乳动物,从那里我想在另一个类上调用正确的重载:

public void Feed(Mammal mammal) {
    // if mammal is a cat, call the AnotherClass.GiveFood overload for cat,
    // if it's a dog, call the AnotherClass.GiveFood for dog, etc.
}

一种方法是执行以下操作:

public void Feed(Mammal mammal) {
    if (mammal is dog) 
        AnotherClass.GiveFood((Dog)mammal);
    if (mammal is Cat) 
        AnotherClass.GiveFood((Cat)mammal);
}

...但我实际上有大量来自哺乳动物的动物。有没有更好的方法来做我想要在 Feed() 中做的事情?有什么办法可以避免 Feed() 最终成为一个充满这些“如果 x 是 y 则调用 z”语句的巨大丑陋方法?

4

5 回答 5

8

我通常不喜欢使用dynamic,但这是我认为合适的情况之一:

public void Feed(Mammal mammal) {
  Anotherclass.GiveFood((dynamic)mammal);
}

这将在运行时解决正确的重载,而无需提前知道类型。

严格来说,这可能不会是最快的方法,但正如您所指出的,替代方案可能很难维护,和/或难以阅读。在这种情况下,动态调度很优雅,并且会自动合并您将来添加的任何重载。

正如 Chris Sinclair 指出的那样,您还可以添加一个包罗万象的方法来检测任何无效调用,并提供比在找不到匹配GiveFood()重载时收到的运行时错误更友好的异常:

public static class AnotherClass
{
  public static void GiveFood(Cat cat) {}
  public static void GiveFood(Dog dog) {}

  public static void GiveFood(Mammal mammal)
  {
    throw new AnimalNotRecognizedException("I don't know how to feed a " + mammal.GetType().Name + ".");
  }
}
于 2013-05-23T17:10:57.500 回答
5

我认为处理食物是动物的责任,而不是喂食者。否则你会遇到你现在遇到的问题:

public void Feed(Mammal mammal) {
    if (mammal is Duck) 
    {
        ((Duck)mammal).PryOpenBeak();
        ((Duck)mammal).InsertFeedingTube();
        ((Duck)mammal).PourDownFood();
    }
}

等等,虽然鸭子不是哺乳动物。

无论如何,你的Mammal 类应该有一个抽象方法Feed(Food food),而动物本身必须弄清楚如何处理食物。这样,在以后添加新哺乳动物时,您就不必使用该新哺乳动物的喂养逻辑来更新喂食器。

@Chris 的评论:那么动物可以实现IFoodXEater包含Feed(IFoodX)方法的正确接口,然后喂食器可以查找它,尽管你又回到了第一方:

if (mammal is IFishEater)
{
    ((IFishEater)mammal).Feed(new Fish());
}
于 2013-05-23T17:14:12.637 回答
2

如果您不介意创建类型映射的工作,您可以像这样伪造双重调度:

[编辑] 这个新的改进版本可以更好地处理子类。如果你有一个派生自另一个哺乳动物类的类(例如下面示例中的Pug派生自Dog),那么你不需要显式地为类添加一个 feeder Pug - 它会自动调用它的基类的 feeder Dog,.

但是,如果需要,您可以为派生类提供特定的馈送器,如下面的Manx类所示。

不过使用dynamic起来要容易得多!我只是想展示如果你不使用它会是什么样子dynamic

using System;
using System.Collections.Generic;

namespace Demo
{
    public class Mammal {}

    public class Cat: Mammal {}
    public class Pig: Mammal {}
    public class Dog: Mammal {}

    public class Pug:  Dog {}
    public class Manx: Cat {}

    public static class Feeder
    {
        static readonly Dictionary<Type, Action<Mammal>> map = createMap();

        static Dictionary<Type, Action<Mammal>> createMap()
        {
            return new Dictionary<Type, Action<Mammal>>
            {
                {typeof(Cat),  mammal => GiveFood((Cat)  mammal)},
                {typeof(Dog),  mammal => GiveFood((Dog)  mammal)},
                {typeof(Manx), mammal => GiveFood((Manx) mammal)}
            };
        }

        public static void GiveFood(Mammal mammal)
        {
            for (
                var currentType = mammal.GetType(); 
                typeof(Mammal).IsAssignableFrom(currentType);
                currentType = currentType.BaseType)
            {
                if (map.ContainsKey(currentType))
                {
                    map[currentType](mammal);
                    return;
                }
            }

            DefaultGiveFood(mammal);
        }

        public static void DefaultGiveFood(Mammal mammal)
        {
            Console.WriteLine("Feeding an unknown mammal.");
        }

        public static void GiveFood(Cat cat)
        {
            Console.WriteLine("Feeding the cat.");
        }

        public static void GiveFood(Manx cat)
        {
            Console.WriteLine("Feeding the Manx cat.");
        }

        public static void GiveFood(Dog dog)
        {
            Console.WriteLine("Feeding the dog.");
        }
    }

    class Program
    {
        void test()
        {
            feed(new Cat());
            feed(new Manx());
            feed(new Dog());
            feed(new Pug());
            feed(new Pig());
            feed(new Mammal());
        }

        void feed(Mammal mammal)
        {
            Feeder.GiveFood(mammal);
        }

        static void Main()
        {
            new Program().test();
        }
    }
}
于 2013-05-23T17:35:04.913 回答
2

我的建议:

第 1 步:创建接口 IMammal

<!-- language: c# -->
public interface IMammal
{
    void Feed();
}

第 2 步:(可选)实现基类 BaseMammal

public class BaseMammal : IMammal
{
    public void Feed()
    {
        Trace.Write("basic mammal feeding");
        //a basic implementation of feeding, common to all or most mammals
    }
}

第 3 步:实现继承的类

public class Cat : BaseMammal
{
    public void Feed()
    {
        Trace.Write("cat feeding");
        BePicky();//some custom cat like functionality
        base.Feed(); //and afterwards its still just a mammal after all
    }
}

public class Gruffalo : BaseMammal
{
    public void Feed()
    {
        Trace.Write("Gruffalo feeding");
        WeirdWayOfEating();//the base implementation is not appropriate
    }
}

第 4 步:使用!(包括随机示例)

List<IMammal> pets = new List<IMammal>()
    {
        new Cat(catValues),
        new Gruffalo(gruffaloValues)
    };

foreach(var pet in pets)
{
    pet.Feed();
}

每只动物都将由它们自己的实现来喂养。你瞧——你的复杂代码现在很简单。我还建议您阅读“Head First Design Patterns”,它解释了这个和许多其他概念。http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124

于 2013-05-23T17:59:02.020 回答
1

如果不止一只动物共享进食行为,我建议使用策略模式将进食行为封装在一个接口中,并为每组动物具体实现每个行为

您将使用组合而不是继承

检查这个设计模式的首要设计模式,我认为在你的情况下这将是一个很好的实现

于 2013-05-23T17:47:09.190 回答