[英]I have a base class. How do I call the right method for the right derived class?
Obviously trying to simplify the problem here. 显然试图在这里简化问题。 I have a base class and a number of derived classes: 我有一个基类和一些派生类:
public class Mammal { }
public class Cat : Mammal { }
public class Dog : Mammal { }
And a utility class: 和实用类:
public static class AnotherClass
{
public static void GiveFood(Cat cat) {}
public static void GiveFood(Dog dog) {}
}
Somewhere else is a method, Feed, which takes a Mammal, and from within there i want to call the right overload on AnotherClass: 在其他地方是一个方法,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.
}
One way to do that would be to do something like: 一种方法是做类似的事情:
public void Feed(Mammal mammal) {
if (mammal is dog)
AnotherClass.GiveFood((Dog)mammal);
if (mammal is Cat)
AnotherClass.GiveFood((Cat)mammal);
}
...but I actually have a huge number of animals derived from Mammal. ......但实际上我有很多来自哺乳动物的动物。 Is there a nicer way to do what I want to do in Feed()? 有没有更好的方法在Feed()中做我想做的事情? Is there any way I can avoid having Feed() end up being a huge ugly method filled with these "if x is y then call z"-statements? 有没有什么方法可以避免Feed()最终成为一个巨大的丑陋方法填充这些“如果x是y然后调用z” - 陈述?
I don't usually like using dynamic
, but this is one of the cases where I think it's appropriate: 我通常不喜欢使用dynamic
,但这是我认为合适的情况之一:
public void Feed(Mammal mammal) {
Anotherclass.GiveFood((dynamic)mammal);
}
That will resolve the correct overload at runtime, without knowing the type in advance. 这将在运行时解决正确的重载,而无需事先知道类型。
Strictly speaking, this probably isn't going to be the fastest method, but as you point out, the alternatives can be a real pain to maintain, and/or hard to read. 严格地说,这可能不是最快的方法,但正如你所指出的,替代方案可能是维护和/或难以阅读的真正痛苦。 In this case, dynamic dispatch is elegant and will automatically incorporate any overloads you add in the future. 在这种情况下,动态调度是优雅的,并将自动合并您将来添加的任何重载。
As Chris Sinclair points out, you could also add a catchall method to detect any invalid calls and provide a friendlier exception than the runtime error you'd receive if no matching GiveFood()
overload could be found: 正如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 + ".");
}
}
I think it's the animal's responsibility to process food, not the feeder. 我认为处理食物是动物的责任,而不是饲养者。 Otherwise you'll run into the problem you now have: 否则你会遇到你现在遇到的问题:
public void Feed(Mammal mammal) {
if (mammal is Duck)
{
((Duck)mammal).PryOpenBeak();
((Duck)mammal).InsertFeedingTube();
((Duck)mammal).PourDownFood();
}
}
And so on, although ducks aren't mammals. 等等,虽然鸭子不是哺乳动物。
Anyway, your Mammal
class should have an abstract method Feed(Food food)
, and the animal itself will have to figure out how to process the food. 无论如何,你的Mammal
类应该有一个抽象的方法Feed(Food food)
,动物本身将不得不弄清楚如何处理食物。 This way when later adding a new mammal, you won't have to update the feeder with the feeding logic for this new mammal. 这样,当后来添加一种新的哺乳动物时,您不必使用这种新哺乳动物的喂养逻辑来更新喂食器。
@Chris's comment: then the animal could implement the proper IFoodXEater
interface that contains a Feed(IFoodX)
method, and then the feeder can look that up, although then you're back at square one: IFoodXEater
的评论:然后动物可以实现包含Feed(IFoodX)
方法的正确IFoodXEater
接口,然后接收器可以查看它,尽管那时你回到了方形一:
if (mammal is IFishEater)
{
((IFishEater)mammal).Feed(new Fish());
}
If you don't mind the effort of creating a type map, you can fake double dispatch like so: 如果您不介意创建类型映射的工作,您可以伪造双重调度,如下所示:
[EDIT] This new, improved version handles subclasses better. [编辑]这个新的改进版本更好地处理子类。 If you have a class derived from another mammal class (such as Pug
derived from Dog
in the example below) then you don't need to explicitly add a feeder for class Pug
- it will automatically call the feeder for its base class, Dog
. 如果你有一个派生自另一个哺乳动物类的类(例如下面例子中派生自Dog
Pug
),那么你不需要为Pug
类明确添加一个馈线 - 它会自动调用它的基类Dog
的馈线。
But you can have a specific feeder for a derived class if you want, as demonstrated by the Manx
class below. 但是,如果需要,您可以为派生类创建特定的进纸器,如下面的Manx
类所示。
Using dynamic
is much much easier though! 使用dynamic
虽然容易得多! I just wanted to show how it could look if you weren't using 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();
}
}
}
My Recommendation: 我的建议:
Step 1 : Create an interface IMammal 第1步 :创建一个接口IMammal
<!-- language: c# -->
public interface IMammal
{
void Feed();
}
Step 2 : (Optional) Implement a Base class BaseMammal 第2步 :(可选)实现Base类BaseMammal
public class BaseMammal : IMammal
{
public void Feed()
{
Trace.Write("basic mammal feeding");
//a basic implementation of feeding, common to all or most mammals
}
}
Step 3 : Implement your inherited classes 第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
}
}
Step 4 : Use! 第4步 :使用! (random example included) (包括随机示例)
List<IMammal> pets = new List<IMammal>()
{
new Cat(catValues),
new Gruffalo(gruffaloValues)
};
foreach(var pet in pets)
{
pet.Feed();
}
Each animal will be fed by their own implementation. 每只动物都将通过自己的实施方式喂养。 Lo and behold - your complex code is now simple. 瞧,你的复杂代码现在很简单。 I would also recommend that you read "Head First Design Patterns", which explains this and many other concepts. 我还建议你阅读“Head First Design Patterns”,它解释了这个和许多其他概念。 http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124 http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124
If more than one animal shares the feeding behavior, I'll suggest to use the strategy pattern to encapsulate the feeding behavior in an interface and concrete implement each behavior for each group of animals 如果不止一只动物分享喂养行为,我建议使用策略模式将喂养行为封装在界面中并具体实现每组动物的每种行为
you will be using composition instead of inheritance 你将使用组合而不是继承
check the head first design patterns for this one I think it will be a good implementation in your case 检查头部的第一个设计模式我认为这将是一个很好的实现在你的情况下
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.