如何改变这种设计以避免沮丧?

发布于 2024-07-15 09:14:35 字数 946 浏览 10 评论 0原文

假设我有一个全部继承自基类的对象集合。 就像……

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

}

当然,这不会编译,因为这会迫使他们沮丧。

感觉好像有一种设计模式或其他一些泛型解决方案可以帮助我解决这个问题,但我还没有付诸实践。

建议?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

丑丑阿 2024-07-22 09:14:35

Linq 惯用法中使用的检查型向下转型是完全安全的。

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

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

为了使其可扩展,您需要一个动物喂食器的字典

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 支持的接口 - 基本上是相同的想法,所以我这里的例子保持简单。

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.

始终不够 2024-07-22 09:14:35

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

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.

演多会厌 2024-07-22 09:14:35

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

也就是说,我确信这个故事比您所描述的更多,并且您有充分的理由无法进行“适当的”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 + ".");
    }      
}

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

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.

晒暮凉 2024-07-22 09:14:35

仅当您有一个“狗列表”并且想要调用一个参数为“动物列表”的方法时,泛型才有用(即 List其中 T : Animal ) - 我认为这没有帮助。

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

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...

哭了丶谁疼 2024-07-22 09:14:35

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

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.

任性一次 2024-07-22 09:14:35

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文