Хороший ОО дизайн - шаблон проектирования Singleton - PullRequest
19 голосов
/ 30 октября 2010

Внезапно у меня возник небольшой кризис ОО.За последние пару лет я довольно хорошо использовал объекты Singleton.Я использовал их во многих местах.

Например, при разработке Java-приложения MVC я бы создал Singleton-класс SystemRegistry для хранения модели и просмотра классов (я работал только над простыми приложениями инеобходимость в нескольких видах никогда не возникала).

Когда я создаю свою модель и смотрю объекты (которые не были одиночными, а просто обычными объектами), я делал бы что-то вроде:

SystemRegistry.getInstance().setModel(model);

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

SystemRegistry.getInstance().getView();

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

Из того, что я прочитал (особенно )Статья Стива Йегге ), похоже, плохой способ разработки моего приложения.Любые идеи относительно лучших способов структурирования моего кода.

Кроме того, еще одним аспектом того, как я проектирую классы, которые могут или не могут быть связаны с Singletons, является использование классов 'типа менеджера'.Примером является (очень простой) игровой движок на основе OpenGL, который я создал на C ++.

Основным классом был GameEngine.Это был перегруженный класс, который хранил кучу Менеджеров и обрабатывал основной цикл, а что нет.Некоторыми менеджерами, хранящимися в этом классе, были такие вещи, как: ObjectManager, RenderingManager, LightingManager, EventManager (включает ввод), HUDManager, FrameRateManager, WindowManager и т. Д. Вероятно, было и еще несколько.

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

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

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

Player player = new Player();
gameEngine.getObjectManager().addObject(player);

Этот объект теперь будет храниться в векторе в классе ObjectManager и будет отрисован, когда GameEngine вызовет метод drawObjects () ObjectManager вкаждый кадр.

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

Любые комментарии к моему сообщению будут высоко оценены.

Редактировать: Спасибо за ответы.Я очень ценю их.Если возможно, я бы хотел, чтобы кто-нибудь дал мне несколько советов относительно двух сценариев проектов, опубликованных выше.Как я мог избежать использования Singletons / Managers?

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

Во втором случае, как еще можно было структурировать приложение?Является ли схватка просто использованием классов Manager в отличие от более конкретных имен?Или же в некоторых случаях классы могут быть дополнительно разбиты (например, ObjectHolder, ObjectDrawer, ObjectUpdater)?

Ответы [ 6 ]

11 голосов
/ 30 октября 2010

Ну, а почему ты пишешь синглтоны? Если вы понимаете, что пошло не так при вашем синглтоните, вы знаете, на что следует обратить внимание в будущем.

Почему вы на секунду поверили, что синглтоны являются хорошим решением для любой проблемы? Потому что в книге так сказано? Не слепо доверяй книгам. Книги должны обосновывать свои требования, как и все остальные. Если я скажу вам, что ваш код выглядит намного лучше, если вы перевернете монитор вверх дном, бремя доказательств лежит на мне. Даже , если я какой-то бог кода. Даже если мне ежедневно поклоняются миллионы разработчиков по всему миру, мой совет все равно ничего не стоит, если я не могу оправдать это, и если я не могу заставить вас пойти "хорошо, это имеет смысл. Я понимаю почему вы рекомендуете это, а я не могу придумать лучшего способа сделать это ".

И то же самое относится к определенной книге шаблонов дизайна. Поэтому они написали, что «шаблоны проектирования потрясающие», и что «синглтон - это шаблон дизайна, и, следовательно, он тоже потрясающий». И что? Могут ли они обосновать это утверждение (нет, не могут, по крайней мере, в случае синглтона), поэтому проигнорируйте его.

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

И если вы сами пришли к оправданию, можете ли вы сегодня увидеть, что с ним не так? Что вы забыли принять во внимание?

Единственный способ избежать слишком большого количества ошибок в будущем - это учиться на ошибках, которые вы уже сделали. Как синглтоны или классы менеджеров ускользали от вашей защиты? На что вы должны были обращать внимание, когда впервые узнали о них? Или, если на то пошло, позже, когда вы свободно использовали их в своем коде. Какие были предупреждающие признаки того, что должен заставить вас задуматься: "Действительно ли эти одинокие звуки - правильный путь?" Как программист, вы должны доверять своему разуму. Вы должны верить, что вы будете принимать разумные решения. И единственный способ сделать это - учиться на плохих решениях, когда вы их принимаете. Потому что мы все делаем, слишком часто. Лучшее, что мы можем сделать, - это убедиться, что мы не будем принимать одни и те же плохие решения неоднократно .

Что касается классов менеджера, их проблема в том, что каждый класс должен нести только одну ответственность. Какова ответственность «менеджера класса»? Это .... эээ ... управляет вещами. Если вы не можете определить лаконичную зону ответственности, значит, класс неправильный. Что именно нужно управлять этими объектами? Что значит управлять ими 1026 *? Стандартная библиотека уже предоставляет контейнерные классы для хранения группы объектов.

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

Чем еще нужно управлять? Узнайте, что означает «управление» этими объектами, и тогда вы узнаете, какие классы вам нужны для управления. Скорее всего, это не единственная монолитная задача, а несколько разных видов обязанностей, которые следует разделить на разные классы.

Что касается синглетонов, я не буду повторять то, что говорил много раз раньше, так что вот ссылка .

Последний совет:

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

Посмотрите на другие парадигмы. Если вы работаете в C ++, вам нужно знать об общем программировании. STL и Boost являются хорошими примерами того, как использовать универсальное программирование для написания кода для решения многих проблем, которые одновременно являются более чистыми, качественными и эффективными, чем эквивалентный код ООП.

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

И иногда простое старое процедурное программирование - это просто хороший и простой инструмент, который вам нужен.

Ключ к «хорошему дизайну» - это , а не для попытки «хорошего ОО-проектирования».Если вы сделаете это, вы заперетесь в этой единой парадигме.Вы могли бы также попытаться найти хороший способ построить дом, используя молоток.Я не говорю, что это невозможно, но есть гораздо лучшие способы построить гораздо лучшие дома, если вы также позволите себе использовать другие инструменты.

Что касается вашего редактирования:

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

DI был бы one вариантом для избежания одиночных переходов.Но не забывайте о старомодной низкотехнологичной опции: просто вручную передайте ссылку на объекты, которые должны знать о вашем «бывшем» синглтоне.

Ваш рендерер должен знать о списке игровых объектов,(Или это действительно так? Может быть, у него есть только один метод, которому нужно дать список объектов. Возможно, сам по себе класс рендерера в этом не нуждается). Поэтому конструктору рендерера должна быть дана ссылка на этот список.Это действительно все, что нужно сделать.Когда X требуется доступ к Y, вы передаете ему ссылку на Y в конструкторе параметра функции.Хорошая особенность этого подхода по сравнению с DI заключается в том, что вы делаете свои зависимости болезненно явными.Вы знаете , что средство визуализации знает о списке визуализируемых объектов, потому что вы можете видеть, как ссылка передается конструктору.И вам пришлось написать эту зависимость, что дало бы вам прекрасную возможность остановиться и спросить "это необходимо?"И у вас есть мотивация для устранения зависимостей: это означает, что нужно меньше печатать!

Многие из зависимостей, с которыми вы сталкиваетесь при использовании синглетонов или DI, не нужны.Оба эти инструмента позволяют легко и просто распространять зависимости между различными модулями, и это то, что вы делаете.И тогда вы получите дизайн, в котором ваш рендерер знает о вводе с клавиатуры, а обработчик ввода должен знать, как сохранить игру.

Другим возможным недостатком DI является простой вопрос технологии.Не все языки имеют хорошие библиотеки DI.Некоторые языки делают почти невозможным создание надежной и универсальной библиотеки DI.

Во втором случае, как еще можно было структурировать приложение?Является ли схватка просто использованием классов Manager в отличие от более конкретных имен?Или это то, что в некоторых случаях классы могут быть дополнительно разбиты (например, ObjectHolder, ObjectDrawer, ObjectUpdater)?

Я думаю, просто переименовать их - хорошее начало, да.Как я уже говорил выше, что значит «управлять» вашими объектами?Если я буду управлять этими объектами, что я буду делать?Три упомянутых вами класса звучат как хорошее разделение.Конечно, когда вы копаетесь в разработке этих классов, вы можете удивиться, почему вам даже нужно и ObjectHolder.Разве это не то, что делают стандартные контейнеры библиотеки / классы коллекции?Вам нужен специальный класс для «удержания объектов», или вы можете просто отказаться от использования List<GameObject>?

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

Представьте, что вы отложили свой код дляполгода.Когда вы вернетесь к этому, у вас будет любая идея, какова была цель класса "ObjectManager"?Возможно, нет, но у вас будет довольно хорошее представление о том, для чего предназначен «ObjectRenderer».Рендерит объекты.

4 голосов
/ 31 октября 2010

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

Не путайте наличие только одного экземпляра класса с одноэлементным шаблоном. В вашем случае хорошо иметь класс менеджера, который управляет всеми видами вещей. Также совершенно здорово иметь только один экземпляр для всего вашего приложения. Но классы, которые используют менеджер, не должны полагаться на существование ровно одного глобального объекта (будь то статическая переменная для класса или глобальная переменная, к которой обращаются все). Чтобы смягчить это, заставьте их требовать ссылки на объект менеджера при их создании. Все они могут получить ссылку на один и тот же объект, им все равно. Это имеет два отличных побочных эффекта. Во-первых, вы можете лучше протестировать свои классы, потому что вы можете легко внедрить объект mock-manager. Второе преимущество заключается в том, что, если вы решите это позже, вы все равно сможете использовать более одного объекта менеджера (например, в многопоточных сценариях) без необходимости менять клиентов.

3 голосов
/ 30 октября 2010

Стив Йегге прав. Google идет еще дальше: у них есть одноэлементный детектор , чтобы помочь искоренить их.

Я думаю, что есть несколько проблем с синглетонами:

  1. Сложнее проверить.
  2. Stateful Singletons должны быть осторожны с безопасностью потоков; легко стать узким местом.

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

1 голос
/ 30 октября 2010

У меня была похожая реализация в последнее время, когда я просматривал один из моих проектов и видел рост числа «менеджеров» (в основном, одиночных игр). Похоже, что у вас двоякое беспокойство - во-первых, стоит ли вообще использовать глобальное состояние (приводящее к классам типа менеджера), а во-вторых, являются ли одиночные пакеты лучшим выбором для глобального состояния.

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

С другой стороны, нужно учитывать зло глобального государства. Это скрывает зависимости. Как только вы вводите глобальные переменные, функции больше не являются черными ящиками, вход которых ограничен их параметрами. И если у вас есть изменяемое глобальное состояние, к которому могут обращаться несколько классов в нескольких потоках в любом порядке, у вас может возникнуть кошмар отладки. И все это затрудняет понимание / тестирование.

При выборе глобального состояния иногда статический класс является лучшим выбором, чем одноэлементный (есть и другие варианты). С одной стороны, это сохраняет дополнительные вызовы GetInstance () ... и для меня это просто более естественно. Но стоит отметить, что синглтоны автоматически допускают отложенную загрузку, что может быть важно, если инициализация вашей глобальной сущности довольно тяжелая. Кроме того (и в некоторой степени придумано здесь), если вам нужно предоставить варианты своего глобального поведения, вы можете просто создать подкласс для своего синглтона.

0 голосов
/ 31 октября 2010

двухступенчатый ответ:

1) http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/

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

2) http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/

Глобальному доступу противодействует использование внедрения зависимостей.

0 голосов
/ 31 октября 2010

Способ, которым я решаю, хочу ли я синглтон или нет, состоит в том, когда я отвечаю на вопрос: «Если бы я создавал экземпляр моего приложения дважды за одну и ту же среду выполнения, имеет ли это смысл?», Если ответ «да», тогда я использую это.

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