Подтвердить что ты не робот

ООП: самонастраивающиеся формы и лающие собаки

Большинство книг по объектно-ориентированному программированию, которые я читал, использовали либо класс 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() статическим? Пропустить функции указателей функций? Have Dog.talk() вернуть имя файла, которое он хочет воспроизвести, и позволить другой части программы справиться с этим?

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

Один из способов может заключаться в том, чтобы конструктор 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();

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

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