ООП: самопишущие фигуры и лай собак - PullRequest
19 голосов
/ 27 февраля 2010

Большинство книг по объектно-ориентированному программированию, которые я читал, использовали либо класс Shape с функцией-членом Shape.draw(), либо класс Dog с функцией-членом Dog.talk(), либо что-то подобное, чтобы продемонстрировать понятие полиморфизма. Теперь это стало источником путаницы для меня, которое не имеет ничего общего с полиморфизмом.

class Dog : public Animal
{  
  public:
  ...
    virtual void talk() { cout << "bark! bark!" << endl; }
  ...
};

Хотя это может работать как простой пример, я просто не могу представить себе хороший способ сделать эту работу в более сложном приложении, где Dog.talk () может потребоваться доступ к звуковым подпрограммам другого класса, например, играть bark.mp3 вместо использования cout для вывода. Допустим, у меня есть:

class Audio
{
   public:
   ...
     void playMP3(const string& filename)
   ...
};

Что было бы хорошим способом доступа к Audio.playMP3() из Dog.talk () во время разработки? Сделать Audio.playMP3() статичным? Передать функциональные указатели? Dog.talk() вернет имя файла, который он хочет воспроизвести, и разрешит другой части программы разобраться с этим?

Ответы [ 6 ]

10 голосов
/ 27 февраля 2010

Одним из способов может быть использование конструктора Dog ссылки на экземпляр класса Audio, потому что собаки (обычно) издают шум:

class Dog: public Animal {
public:
    Dog(Audio &a): audio(a) {}
    virtual void talk() { audio.playMP3("bark.mp3"); }
private:
    Audio &audio;
};

Вы можете использовать это так:

Audio audioDriver;
Dog fido(audioDriver);
fido.talk();
9 голосов
/ 28 февраля 2010

Мое решение было бы, чтобы класс Dog передавал аудиоустройство в функции лая.

Собака должна не хранить указатель на аудиоустройство все время, это не входит в ее обязанности. Если вы пойдете по этому пути, вы получите конструктор, который получит два десятка объектов, по сути, указывающих на все остальное приложение (ему также нужен указатель на средство визуализации, поэтому его можно нарисовать. Ему нужен указатель на землю, и менеджеру ввода, говорящему, куда идти, и ............ Безумие так лжет.

Ничего из этого не принадлежит собаке. Если ему нужно связаться с другим объектом, передайте этот объект определенному методу, который нуждается в этом.

Собака несет ответственность за лай. Кора издает звук. Таким образом, методу лая нужен способ генерирования звука: ему должна быть передана ссылка на аудиообъект. Собака в целом не должна заботиться или знать об этом.

class Dog: public Animal {
public:
    virtual void talk(Audio& a);
};

По той же логике фигуры не должны рисовать себя. Рендерер рисует объекты, вот для чего он. Ответственность за прямоугольный объект заключается в том, чтобы быть прямоугольным. Часть этой обязанности заключается в том, чтобы иметь возможность передавать необходимые данные чертежа для средства визуализации, когда он хочет нарисовать прямоугольник, но сам рисунок не является его частью.

3 голосов
/ 28 февраля 2010

Это действительно интересный вопрос, поскольку он затрагивает элементы дизайна и абстракции. Например, как вы объединяете объект Dog, чтобы сохранить контроль над тем, как он создается? Какой тип аудио объекта он должен поддерживать и должен ли он «лаять» в MP3, WAV и т. Д.?

Стоит немного прочесть о Инверсия управления и Инъекция зависимости , так как многие проблемы, о которых вы думаете, были продуманы совсем немного. Существует немало последствий, таких как гибкость, ремонтопригодность, тестирование и т. Д.

1 голос
/ 28 февраля 2010

Интерфейс обратного вызова был предложен в нескольких других ответах, но он имеет недостатки:

  • Множество (потенциально значительно) разных классов, использующих один и тот же интерфейс. Эти классы по-разному могут испортить ясность интерфейса. То, что началось с PlaySound (sound_name), становится PlaySound (строка sound_name, bool reverb, float max_level, vector direction, bool looping, ...) с помощью множества других методов (StopSound , RestartSound и т. Д.)
  • Изменения в аудиоинтерфейсе перестроят все, что знает об аудиоинтерфейсе (я считаю, что это имеет значение с C ++)
  • Предоставленный интерфейс работает только для аудиосистемы (ну, это должно быть только для аудиосистемы). А как насчет видеосистемы и сетевой системы?

Одна альтернатива, которая также была упомянута, - сделать вызовы аудиосистемы статическими (или интерфейс аудиосистемы одноэлементными). Это позволит упростить конструкцию собаки (создание собаки больше не требует знания аудиосистемы), но не решит ни одну из проблем, перечисленных выше.

Мое предпочтительное решение - делегаты. Собака определяет свой общий выходной интерфейс (IE Bark (t_barkData const & data); Growl (t_growlData const & data)), и другие классы подписываются на этот интерфейс. Системы делегатов могут стать довольно сложными, но при правильной реализации они не сложнее в отладке, чем интерфейс обратного вызова, сокращают время перекомпиляции и улучшают читаемость.

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

Отличной общей реализацией являются сигналы и слоты QT, но реализовать что-то столь мощное самостоятельно будет сложно. Если вам нужен простой пример чего-то подобного в c ++, я бы подумал опубликовать его, но если вам это не интересно, я не собираюсь тратить время на субботу:)

Некоторые недостатки для делегатов (от макушки головы): 1. Вызвать накладные расходы, для вещей, которые происходят тысячи раз в секунду (IE «рисует» операции в механизме рендеринга), это необходимо учитывать. Большинство реализаций медленнее, чем виртуальные функции. Эти накладные расходы совершенно незначительны для операций, которые происходят не очень часто. 2. Генерация кода, в основном ошибка ограниченной поддержки указателей в функциях C ++. Шаблоны - это практически требование для реализации легко читаемой переносимой системы делегатов.

0 голосов
/ 27 февраля 2010

Основной ответ заключается в том, что Animal инициализируется либо объектом Audio, либо более сложным объектом, который содержит несколько Audio. Затем функция разговора животного вызывает метод для этого объекта Audio, чтобы создать шум разговора для животного.

Объект Dog инициализирует Animal конкретным экземпляром объекта Audio, характерным для Dogs, или (в более сложных случаях) принимает параметры, позволяющие ему построить объект Audio для передачи Animal.

0 голосов
/ 27 февраля 2010

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

Подход со статическим playMP3 методом в порядке. Использование глобального эталона для вашей аудиосистемы прекрасно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...