2010-10-30 3 views
19

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

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

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

SystemRegistry.getInstance().setModel(model); 

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

SystemRegistry.getInstance().getView(); 

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

Из того, что я читал (в частности, Steve Yegge's article), это кажется плохим способом разработки моего приложения. Любые идеи относительно лучшего способа структурирования моего кода.

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

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

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

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

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

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

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

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

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

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

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

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

ответ

11

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

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

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

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

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

Слишком много ошибок Ошибки в будущем должны учиться на ошибках, которые вы уже сделали. Как синглтоны или классы менеджеров проскальзывают благодаря вашей защите? Что вы должны были наблюдать, когда вы впервые узнали о них? Или, если на то пошло, позже, когда вы использовали их в своем коде. Какие предупредительные знаки были в том, что должны были спросить вас: «Эти синглы действительно правильные пути?» Как программист, вы должны доверять своему разуму. Вы должны верить, что вы примете разумные решения. И единственный способ сделать это - учиться на плохих решениях, когда вы их делаете. Потому что все мы делаем это слишком часто. Лучшее, что мы можем сделать, это убедиться, что мы не будем принимать те же плохие решения повторно.

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

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

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

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

Один последний совет:

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

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

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

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

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

Как для Edit:

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

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

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

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

Еще один возможный недостаток DI - простой вопрос технологии. Не все языки имеют хорошие библиотеки DI. Некоторые языки делают невозможным создание надежной и общей библиотеки DI.

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

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

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

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

+4

+1 «Винт ООП» - это глоток свежего воздуха. Слишком много людей автоматически и безоговорочно приравнивают «ООП» к «хорошему». Бьярне Страуструп писал об этом не один раз. – fredoverflow

+0

Я не понимаю, «хорошая вещь об этом подходе, по сравнению с DI, заключается в том, что вы делаете свои зависимости болезненно явными». Это то, что вы делаете в DI; ваши классы задают свои зависимости либо в параметрах конструктора, и в установках, либо в (yick) частных переменных.Вы можете сделать это без DI _framework_, но это может быть сложно. Выполнение этого вручную часто приводит к тому, что класс A принимает класс B в конструкторе, когда единственное использование B - передать его конструктору класса C. – NamshubWriter

+0

@NamshubWriter: я считаю, что с картой DI класс регистрирует свои зависимости , конечно, и тогда они как бы «волшебным образом появляются». Вам не нужно передавать их с того места, где они созданы, где они нужны, поэтому вы не можете «почувствовать» зависимость, как вы делаете, если единственный способ распространения зависимостей - передать их как параметры. DI затрудняет просмотр информации о том, откуда появилась зависимость *. И это не дает вам четкого способа отслеживать, где определенный объект используется как зависимость. Все зависит от большого черного ящика глобального состояния, которое свободно распространяется на зависимости. – jalf

3

Steve Yegge является правильным. Google идет еще дальше: у них есть Singleton detector, чтобы помочь их искоренить.

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

  1. Harder, чтобы проверить.
  2. Должностные лица должны быть осторожны с безопасностью резьбы; легко стать узким местом.

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

+0

+1 Но если у вас есть потребность только в одном, инъекция зависимостей - это путь, и с современными каркасами DI добавляется только так мало с точки зрения сложности. – helpermethod

+0

Точно. У Весны есть Синглтоны, но они не то же самое, что писать самостоятельно. – duffymo

+0

Даже когда вы «должны иметь только один», это еще не значит, что вам нужен синглтон. Даже без DI, передача еще одного аргумента конструктору обычно не такая большая сделка, как может показаться впереди. – jalf

1

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

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

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

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

0

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

0

двухэтапного-ответ:

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

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

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

Глобальный доступ противопоставлен с помощью инъекции зависимостей.

+1

Можете ли вы привести мне пример того, когда вам нужно «обеспечить только один экземпляр существует»? Это только хороший дизайн, чтобы гарантировать, что когда вы на самом деле * нуждаетесь *, чтобы гарантировать. И я не могу придумать ни одного случая, в котором вам это нужно. – jalf

+0

Обычными примерами являются «настройки приложения» - вы не хотите, чтобы одна часть приложения меняла цвет фона в одном экземпляре, но другая часть приложения просматривает цвет фона в другом экземпляре - «инвентарь», «система» log "и т. д. –

+0

@jalf Еще один типичный пример - соединение с базой данных – koen

4

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

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

Смежные вопросы