我有一个基类。如何为正确的派生类调用正确的方法?

时间:2013-05-23 17:09:33

标签: c# oop inheritance

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

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,它接受一个哺乳动物,从那里我想在AnotherClass上调用正确的重载:

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”-statements?

5 个答案:

答案 0 :(得分:8)

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

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

这将在运行时解决正确的重载,而不事先知道类型。

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

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

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 + ".");
  }
}

答案 1 :(得分: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)界面,然后喂食器可以查看它,尽管那时你回到了方形一:< / p>

if (mammal is IFishEater)
{
    ((IFishEater)mammal).Feed(new Fish());
}

答案 2 :(得分:2)

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

[编辑]这个新的改进版本更好地处理子类。如果你有一个派生自另一个哺乳动物类的类(例如下面例子中Pug派生的Dog)那么你不需要为类Pug明确添加一个馈线 - 它将自动调用其基类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();
        }
    }
}

答案 3 :(得分: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

答案 4 :(得分:1)

如果不止一只动物分享喂养行为,我建议使用策略模式将喂养行为封装在界面中,具体实现每组动物的每种行为

您将使用合成而不是继承

检查头部的第一个设计模式,我认为这将是一个很好的实现