3

假设我有一个对象集合,它们都继承自一个基类。就像是...

   abstract public class Animal
    {

    }

    public class Dog :Animal
    {

    }

    class Monkey : Animal
    {

    }

现在,我们需要喂养这些动物,但不允许它们知道如何喂养自己。如果可以,答案将很简单:

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

然而,他们不知道如何养活自己,所以我们想做这样的事情:

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

}

当然,这不会编译,因为它会迫使一个沮丧。

感觉好像有一种设计模式或其他一些带有泛型的解决方案可以帮助我解决这个问题,但我还没有把手指放在它上面。

建议?

4

6 回答 6

8

在 Linq 惯用语中使用的经过检查的 downcast 是完全安全的。

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

或者您可以使用访问者模式。访问者对象知道所有类型的动物,因此它对FeedAnimal每种类型都有一个功能。您将访问者对象传递给动物的Feed函数,它会回调正确的FeedAnimal方法,传递this.

为了使其可扩展,您需要一个Dictionary动物喂食器:

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

要注册喂食动作,您首先要执行以下操作:

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

        // give food to m somehow
    };

但是有一个沮丧,你也必须在密钥中给出正确的类型。所以做一个帮手:

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

这捕获了模式,因此您可以完全安全地重用它:

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

然后当你需要喂动物时,使用这个扩展方法:

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

例如

a.Feed();

因此 _feeders 将是与扩展方法相同的静态类中的私有静态字段,以及该AddFeeder方法。

更新:所有代码都在一个地方,还支持继承:

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

您还可以通过每种类型支持的接口循环t- 基本上是相同的想法,所以我在这里保持示例简单。

于 2009-03-22T00:41:34.293 回答
3

这是OOD中的一个经典问题。如果你的类(动物)是固定的,你可以使用访问者模式。如果您的操作集(例如饲料)有限,您只需向 Animal 添加一个方法 feed()。如果这些都不成立,就没有简单的解决方案。

于 2009-03-22T00:42:09.097 回答
3

首先,面向对象设计的要点之一是对象将其数据与作用于该数据的行为捆绑在一起(即“动物知道如何喂养自己”)。所以这是“医生,当我这样做时很痛! - 所以不要那样做”的情况之一。

也就是说,我相信这个故事比你描述的要多,而且你有充分的理由不能做“正确的”OOD。所以你有几个选择。

您可以让 FeedAnimal(Animal a) 方法使用反射来查找动物的类型。基本上你是在 FeedAnimal 方法中做你的多态性。

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

一种更面向对象但更复杂的方法是使用其他人建议的访问者模式。这对于有经验的开发人员来说更优雅,但对于更多的新手程序员来说可能不太明显和可读性。您喜欢哪种方法可能取决于您拥有多少不同的动物类型。

于 2009-03-22T01:10:57.530 回答
1

只有当你有一个“狗列表”并且想要调用一个带有“动物列表”(即List<T> where T : Animal)参数的方法时,泛型才会有帮助 - 我认为它在这里没有帮助。

我怀疑您将需要一个访问者模式......一些可能知道如何喂养给定类型动物的对象,并继续尝试它们,直到找到一个知道如何......

于 2009-03-22T00:43:03.680 回答
0

由于不允许动物喂养自己或其他动物,因此您别无选择,只能创建一个拥有私有方法来喂养动物的“所有者”或“饲养员”或“看护人”类。

于 2009-03-22T00:42:06.163 回答
0

您可以在动物类中包含一个成员变量,该变量将识别动物的类型,然后让 feed 函数读取它并根据它产生不同的结果。

于 2009-03-22T00:45:43.860 回答