Reputation: 227
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:
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()? 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?
Upvotes: 4
Views: 188
Reputation: 3289
I don't usually like using dynamic
, but this is one of the cases where I think it's appropriate:
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:
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 + ".");
}
}
Upvotes: 8
Reputation: 109762
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
.
But you can have a specific feeder for a derived class if you want, as demonstrated by the Manx
class below.
Using dynamic
is much much easier though! I just wanted to show how it could look if you weren't using 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();
}
}
}
Upvotes: 2
Reputation: 51
My Recommendation:
Step 1: Create an interface IMammal
<!-- language: c# -->
public interface IMammal
{
void Feed();
}
Step 2: (Optional) Implement a Base class 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
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! (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. http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124
Upvotes: 2
Reputation: 29
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
Upvotes: 1
Reputation: 151674
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. 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:
if (mammal is IFishEater)
{
((IFishEater)mammal).Feed(new Fish());
}
Upvotes: 5