Как изменить этот дизайн, чтобы избежать уныния? - PullRequest
3 голосов
/ 22 марта 2009

Допустим, у меня есть коллекция объектов, которые все наследуются от базового класса. Что-то вроде ...

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

}

Конечно, это не скомпилируется, так как это вызовет уныние.

Такое ощущение, что есть шаблон проектирования или какое-то другое решение с использованием дженериков, которые помогают мне решить эту проблему, но я еще не указал на это.

Предложения

Ответы [ 6 ]

8 голосов
/ 22 марта 2009

Проверенный downcast, используемый в идиомах Linq, совершенно безопасен.

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 - в основном та же идея, поэтому я сохранил простой пример здесь.

3 голосов
/ 22 марта 2009

Во-первых, одним из основных аспектов объектно-ориентированного проектирования является то, что объекты связывают свои данные с поведением, которое воздействует на эти данные (т. Е. «Животные знают, как себя кормить»). Так что это одна из тех ситуаций "Доктор, мне больно, когда я так делаю! - Так не делай".

Тем не менее, я уверен, что в этой истории есть нечто большее, чем вы описали, и что у вас есть веские причины для того, чтобы вы не смогли сделать «правильный» 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 + ".");
    }      
}

Более объектно-ориентированным, но более сложным способом было бы использовать шаблон Visitor, предложенный другими. Это более элегантно для опытного разработчика, но, возможно, менее очевидно и читаемо для начинающих программистов. Какой подход вы предпочитаете, может зависеть от того, сколько у вас различных типов животных.

2 голосов
/ 22 марта 2009

Это классическая проблема в OOD. Если ваш набор классов (животных) фиксирован, вы можете использовать шаблон посетителя . Если ваш набор действий (например, подача) ограничен, вы просто добавляете метод feed () в Animal. Если ничего из этого не выполняется, простого решения не существует.

1 голос
/ 22 марта 2009

Обобщения будут полезны, только если у вас есть «список собак» и вы хотите вызвать метод с аргументом «список вещей, которые являются животными» (т.е. List<T> where T : Animal) - я не думаю, что это помогает здесь.

Я подозреваю, что вам понадобится шаблон посетителя ... некоторый набор объектов, которые может знают, как кормить данный тип животных, и продолжайте пробовать их, пока вы найти тот, который знает, как ...

0 голосов
/ 22 марта 2009

В класс животных можно включить переменную-член, которая идентифицирует тип животного, а затем функция чтения прочитает его и даст различные результаты на его основе.

0 голосов
/ 22 марта 2009

Поскольку животным запрещено кормить себя или других животных, у вас нет другого выбора, кроме как создать класс «Владелец», «Хранитель» или «Смотритель», который владеет частным методом кормления животных.

...