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

本文关键字:派生 调用 方法 有一个 基类 | 更新日期: 2023-09-27 18:14:58

显然是想简化问题。我有一个基类和一些派生类:

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"的语句?

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

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

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

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

严格地说,这可能不是最快的方法,但正如您指出的那样,替代方法维护起来很痛苦,而且/或者很难阅读。在这种情况下,动态分派是优雅的,并将自动合并您将来添加的任何重载。

正如Chris Sinclair指出的那样,你还可以添加一个catch - all方法来检测任何无效调用,并提供一个比没有匹配的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 + ".");
  }
}

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

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());
}

我的建议:

步骤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();
}

每只动物将由它们自己的实现来喂养。瞧,复杂的代码现在变得简单了。我还建议你阅读"头部优先设计模式",它解释了这个和许多其他概念。http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124

如果您不介意创建类型映射,您可以像这样使用双重分派:

[EDIT]这个新的改进版本更好地处理子类。如果你有一个从另一个哺乳动物类派生的类(比如下面的例子中从Dog派生的Pug),那么你不需要显式地为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();
        }
    }
}

如果不止一种动物具有相同的摄食行为,我建议使用策略模式将摄食行为封装在一个接口中,并具体实现每一组动物的每种行为

你将使用组合而不是继承

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