How to change this design to avoid a downcast?

我们两清 提交于 2020-01-23 02:05:08

问题


Let's say I have a collection of objects that all inherit from a base class. Something like...

   abstract public class Animal
    {

    }

    public class Dog :Animal
    {

    }

    class Monkey : Animal
    {

    }

Now, we need to feed these animals, but they are not allowed to know how to feed themselves. If they could, the answer would be straightforward:

foreach( Animal a in myAnimals )
{
   a.feed();
}

However, they can't know how to feed themselves, so we want to do something like this:

    class Program
{
    static void Main(string[] args)
    {
        List<Animal> myAnimals = new List<Animal>();

        myAnimals.Add(new Monkey());
        myAnimals.Add(new Dog());

        foreach (Animal a in myAnimals)
        {
            Program.FeedAnimal(a);
        }
    }

    void FeedAnimal(Monkey m) {
        Console.WriteLine("Fed a monkey.");
    }

    void FeedAnimal(Dog d)
    {
        Console.WriteLine("Fed a dog.");
    }

}

Of course, this won't compile, as it would be forcing a downcast.

It feels as if there's a design pattern or some other solution with generics that help me out of this problem, but I haven't put my fingers on it yet.

Suggestions?


回答1:


A checked downcast, as used within Linq idioms, is perfectly safe.

foreach (Monkey m in myAnimals.OfType<Monkey>())
    Program.FeedAnimal(m);

Or you could use the visitor pattern. The visitor object knows all the types of animal, so it has a FeedAnimal function for each. You pass the visitor object to an animal's Feed function, and it calls back to the correct FeedAnimal method, passing this.

To make it extensible, you need a Dictionary of animal feeders:

private static Dictionary<Type, Action<Animal>> _feeders;

To register a feeding action, you'd do this to begin with:

_feeders[typeof(Monkey)] = 
    a =>
    {
        Monkey m = (Monkey)a;

        // give food to m somehow
    };

But there's a downcast, and you have to give the correct type in the key as well. So make a helper:

public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) where TAnimal : Animal
{
    _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a);
}

This captures the pattern so you can reuse it completely safely:

AddFeeder<Monkey>(monkey => GiveBananasTo(monkey));
AddFeeder<Dog>(dog => ThrowBiscuitsAt(dog));

Then when you need to feed an animal, use this extension method:

public static void Feed(this Animal a)
{
    _feeders[a.GetType()](a);
}

e.g.

a.Feed();

So _feeders would be a private static field in the same static class as the extension method, along with the AddFeeder method.

Update: all the code in one place, also with support for inheritance:

public static class AnimalFeeding
{
    private static Dictionary<Type, Action<Animal>> _feeders 
        = new Dictionary<Type, Action<Animal>>();

    public static void AddFeeder<TAnimal>(Action<TAnimal> feeder) 
        where TAnimal : Animal
    {
        _feeders[typeof(TAnimal)] = a => feeder((TAnimal)a);
    }

    public static void Feed(this Animal a)
    {
        for (Type t = a.GetType(); t != null; t = t.BaseType)
        {
            Action<Animal> feeder;
            if (_feeders.TryGetValue(t, out feeder))
            {
                feeder(a);
                return;
            }
        }

        throw new SystemException("No feeder found for " + a.GetType());
    }
}

You could also have a loop through the interfaces support by each type t - basically the same idea so I've kept the example simple here.




回答2:


First off, one of the main points of object-oriented design is that objects bundle their data with the behavior that acts on that data (i.e., "animals know how to feed themselves"). So this is one of those "Doctor, it hurts when I do this! - So don't do that" situations.

That said, I'm sure there's more to the story than you've described and that you have good reasons for not being able to do "proper" OOD. So you have a few options.

You can have your FeedAnimal(Animal a) method use reflection to find the type of the animal. Basically you're doing your polymorphism in the FeedAnimal method.

static void FeedAnimal(Animal a)
{
    if (a is Dog)
    {
        Console.WriteLine("Fed a dog.");
    }
    else if (a is Monkey)
    {
        Console.WriteLine("Fed a monkey.");
    }
    else
    {
        Console.WriteLine("I don't know how to feed a " + a.GetType().Name + ".");
    }      
}

A more object-oriented, but more complicated way of doing it would be to use the Visitor pattern suggested by others. This is more elegant to the experienced developer, but arguably less obvious and readable to more novice programmers. Which approach you prefer might depend on how many different Animal types you have.




回答3:


This is a classical problem in OOD. If your set of classes (animals) is fixed, you can use the visitor pattern. If your set of actions (eg. feed) is limited, you just add a method feed() to Animal. If none of this holds, there is no easy solution.




回答4:


Generics would only be helpful if you had a "list of dogs" and wanted to call a method with an argument that was "list of things that are animals" (i.e. List<T> where T : Animal) - I don't think it helps here.

I suspect you are going to need a visitor pattern... some set of objects that may know how to feed a given type of animal, and keep trying them until you find one that knows how...




回答5:


Since Animals aren't allowed to feed themselves or other animals, you have no other option then to create a class "Owner" or "Keeper" or "Caretaker", who owns a private method to feed animals.




回答6:


You could include a member variable in the animal class that would identify the animal's type and then have the feed function read it and produce different results based on it.



来源:https://stackoverflow.com/questions/670331/how-to-change-this-design-to-avoid-a-downcast

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!