简体   繁体   中英

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:

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?

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

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM