В последнее время было много дискуссий о проблемах с использованием (и чрезмерным использованием) Singletons. Я был одним из тех людей в начале моей карьеры тоже. Теперь я вижу, в чем проблема, и все же есть много случаев, когда я не вижу хорошей альтернативы, и не многие из дискуссий по борьбе с синглтоном действительно дают такую возможность.
Вот реальный пример крупного недавнего проекта, в котором я принимал участие:
Приложение представляло собой толстый клиент со множеством отдельных экранов и компонентов, которые используют огромные объемы данных из состояния сервера, которые обновляются не слишком часто. Эти данные в основном кэшировались в объекте «менеджер» Singleton - страшном «глобальном состоянии». Идея заключалась в том, чтобы иметь это единственное место в приложении, которое хранит и синхронизирует данные, и тогда любые новые открытые экраны могут просто запрашивать большую часть того, что им нужно оттуда, без повторяющихся запросов различных вспомогательных данных с сервера. Постоянный запрос к серверу занимал бы слишком большую пропускную способность - и я говорю о дополнительных тысячах интернет-счетов в неделю, так что это было неприемлемо.
Есть ли какой-то другой подход, который мог бы быть уместным здесь, чем в основном наличие такого типа объекта кэша глобального менеджера данных? Этот объект официально не обязательно должен быть «синглтоном», но концептуально имеет смысл быть им. Какая хорошая чистая альтернатива здесь?
источник
Ответы:
Здесь важно различать отдельные экземпляры и шаблон проектирования Singleton .
Отдельные случаи просто реальность. Большинство приложений предназначены для работы только с одной конфигурацией за раз, с одним пользовательским интерфейсом за раз, с одной файловой системой за раз и так далее. Если нужно сохранить много состояний или данных, то, безусловно, вы захотите иметь только один экземпляр и поддерживать его как можно дольше.
Шаблон проектирования Singleton - это очень специфический тип отдельного экземпляра, а именно:
Именно из-за этого конкретного выбора дизайна шаблон представляет несколько потенциальных долгосрочных проблем:
Ни один из этих симптомов на самом деле не является эндемичным для единичных случаев, только паттерн Синглтона.
Что вы можете сделать вместо этого? Просто не используйте шаблон Singleton.
Цитирую из вопроса:
У этой концепции есть название, как вы намекаете, но звучите неуверенно. Это называется кеш . Если вы хотите стать модным, вы можете назвать его «автономным кэшем» или просто автономной копией удаленных данных.
Кэш не должен быть одиночным. Это может потребоваться для одного экземпляра, если вы хотите избежать выборки одних и тех же данных для нескольких экземпляров кэша; но это не значит, что на самом деле вы должны всем разоблачать .
Первое, что я хотел бы сделать, это разделить различные функциональные области кэша на отдельные интерфейсы. Например, допустим, вы создали худший в мире клон YouTube на основе Microsoft Access:
Здесь у вас есть несколько интерфейсов, описывающих конкретные типы данных, к которым конкретному классу может понадобиться доступ - медиа, профили пользователей и статические страницы (например, первая страница). Все это реализовано одним мега-кешем, но вы разрабатываете свои индивидуальные классы так, чтобы вместо них принимались интерфейсы, поэтому им все равно, какой у них экземпляр. Вы инициализируете физический экземпляр один раз, когда запускается ваша программа, а затем просто начинаете передавать экземпляры (приведенные к определенному типу интерфейса) через конструкторы и открытые свойства.
Кстати, это называется инъекция зависимости ; вам не нужно использовать Spring или какой-либо специальный IoC-контейнер, только если ваш общий дизайн класса принимает его зависимости от вызывающей стороны вместо того, чтобы создавать их экземпляры самостоятельно или ссылаться на глобальное состояние .
Почему вы должны использовать дизайн на основе интерфейса? Три причины:
Это облегчает чтение кода; из интерфейсов можно четко понять, от каких данных зависят зависимые классы.
Если и когда вы поймете, что Microsoft Access не был лучшим выбором для серверной части данных, вы можете заменить ее на что-то лучшее - скажем, на SQL Server.
Если и когда вы понимаете , что SQL Server не является лучшим выбором для средств массовой информации специально , вы можете разбить реализацию , не затрагивая другие части системы . Вот где приходит настоящая сила абстракции.
Если вы хотите сделать еще один шаг вперед, вы можете использовать IoC-контейнер (DI-фреймворк), например Spring (Java) или Unity (.NET). Почти каждая инфраструктура DI будет осуществлять свое собственное управление временем жизни и, в частности, позволит вам определить конкретную услугу как отдельный экземпляр (часто его называют «одиночным», но это только для ознакомления). По сути, эти фреймворки избавляют вас от лишней ручной обработки экземпляров, но они не являются строго необходимыми. Вам не нужно никаких специальных инструментов для реализации этого дизайна.
Для полноты картины я должен отметить, что приведенный выше дизайн тоже не идеален. Когда вы имеете дело с кешем (как и вы), у вас фактически должен быть совершенно отдельный слой . Другими словами, такой дизайн:
Преимущество этого в том, что вам даже не нужно разбивать свой
Cache
экземпляр, если вы решите провести рефакторинг; Вы можете изменить способ хранения медиа, просто добавив альтернативную реализациюIMediaRepository
. Если вы подумаете о том, как это согласуется, вы увидите, что он все еще создает только один физический экземпляр кэша, поэтому вам никогда не придется извлекать одни и те же данные дважды.Ничто из этого не говорит о том, что каждый кусочек программного обеспечения в мире должен быть спроектирован в соответствии с этими строгими стандартами высокой когезии и слабой связи; это зависит от размера и масштаба проекта, вашей команды, вашего бюджета, сроков и т. д. Но если вы спрашиваете, какой дизайн лучше (использовать вместо одиночного), то это он.
PS Как уже говорили другие, для зависимых классов, вероятно, не самая лучшая идея знать, что они используют кеш - это деталь реализации, о которой им просто не следует беспокоиться. При этом общая архитектура все равно будет выглядеть очень похоже на то, что изображено выше, вы просто не будете ссылаться на отдельные интерфейсы как на кеши . Вместо этого вы бы назвали их Услуги или что-то подобное.
источник
В случае, если вы даете, звучит так, будто использование Singleton - это не проблема, а симптом проблемы - большая архитектурная проблема.
Почему экраны запрашивают данные из кэша? Кэширование должно быть прозрачным для клиента. Должна быть соответствующая абстракция для предоставления данных, и реализация этой абстракции может использовать кэширование.
Вероятно, проблема заключается в том, что зависимости между частями системы установлены неправильно, и это, вероятно, системный характер.
Почему экраны должны знать, где они получают свои данные? Почему на экранах нет объекта, который может выполнять свои запросы данных (за которыми скрыт кеш)? Зачастую ответственность за создание экранов не централизована, и поэтому нет смысла вводить зависимости.
Опять же, мы смотрим на масштабные архитектурные и дизайнерские вопросы.
Кроме того, очень важно понимать, что время жизни объекта может быть полностью отделено от того, как объект найден для использования.
Кэш должен жить в течение всего времени жизни приложения (чтобы быть полезным), так что время жизни объекта - это время существования Singleton.
Но проблема с Singleton (по крайней мере, общей реализацией Singleton как статического класса / свойства) заключается в том, как другие классы, которые его используют, пытаются найти его.
В статической реализации Singleton принято просто использовать ее везде, где это необходимо. Но это полностью скрывает зависимость и тесно связывает два класса.
Если мы предоставляем зависимость классу, эта зависимость является явной, и весь потребляющий класс должен знать о контракте, доступном для его использования.
источник
Я написал целую главу только по этому вопросу. Главным образом в контексте игр, но большинство из них должно применяться вне игр.
ТЛ; др:
Шаблон Gang of Four Singleton делает две вещи: предоставляет вам удобный доступ к объекту из любого места и гарантирует, что может быть создан только один его экземпляр. В 99% случаев все, что вас волнует, - это первая половина этого, а перемещение по второй половине, чтобы получить его, добавляет ненужные ограничения.
Мало того, но есть лучшие решения для обеспечения удобного доступа. Сделать объект глобальным - это ядерный вариант решения этой проблемы, и он позволяет легко разрушить инкапсуляцию. Все, что плохо в глобалах, полностью применимо к синглетам.
Если вы используете его только потому, что в коде есть много мест, где нужно касаться одного и того же объекта, попробуйте найти лучший способ присвоить его только этим объектам, не раскрывая его всей базе кода. Другие решения:
Отвергни это полностью. Я видел много одноэлементных классов, которые не имеют никакого состояния и являются просто пакетами вспомогательных функций. Этим не нужен экземпляр вообще. Просто сделайте их статическими функциями или переместите их в один из классов, которые функция принимает в качестве аргумента. Тебе не понадобился бы специальный
Math
класс, если бы ты мог просто сделать123.Abs()
.Передай это. Простое решение, если методу нужен какой-то другой объект, - просто передать его. Нет ничего плохого в том, чтобы передать некоторые объекты.
Поместите это в базовый класс. Если у вас много классов, которым нужен доступ к какому-то специальному объекту, и они совместно используют базовый класс, вы можете сделать этот объект членом базового класса. Когда вы его построите, перейдите в объект. Теперь все производные объекты могут получить его, когда им это нужно. Если вы делаете его защищенным, вы гарантируете, что объект все еще остается инкапсулированным.
источник
Проблема не в глобальном состоянии как таковом.
На самом деле вам нужно только беспокоиться
global mutable state
. Постоянное состояние не зависит от побочных эффектов и, следовательно, является меньшей проблемой.Основной проблемой синглтона является то, что он добавляет связывание и, таким образом, затрудняет тестирование (э). Вы можете уменьшить связь, получая синглтон из другого источника (например, с завода). Это позволит вам отделить код от конкретного экземпляра (хотя вы становитесь более привязанными к фабрике (но, по крайней мере, фабрика может иметь альтернативные реализации для разных фаз)).
В вашей ситуации, я думаю, вы можете избежать неприятностей, если ваш синглтон фактически реализует интерфейс (так что альтернатива может быть использована в других ситуациях).
Но еще одним существенным недостатком синглетонов является то, что, как только они на месте, их удаляют из кода и заменяют их чем-то другим, становится очень трудной задачей (снова возникает такая связь).
источник
le weekend
сожалению, это один из наших). Спасибо :-)Тогда что? Так как никто не сказал это: Toolbox . Это если вы хотите глобальные переменные .
Но угадайте что? Это синглтон!
А что такое синглтон?
Может быть, именно здесь начинается путаница.
Для меня синглтон - это объект, который должен иметь только один экземпляр и всегда. Вы можете получить к нему доступ в любом месте, в любое время, без необходимости его создания. Вот почему это так тесно связано с
static
. Для сравнения,static
это в основном то же самое, за исключением того, что это не экземпляр. Нам не нужно его создавать, даже не можем, потому что он автоматически распределен. И это может и действительно приносит проблемы.Исходя из моего опыта, простая замена
static
на Singleton решила много проблем в проекте лоскутного мешка среднего размера, в котором я работаю. Это только означает, что он имеет некоторое использование для плохо разработанных проектов. Я думаю, что слишком много дискуссий о том, полезен ли шаблон синглтона или нет, и я не могу спорить, действительно ли это плохо . Но все же есть хорошие аргументы в пользу одноэлементных по сравнению со статическими методами в целом .Единственное, в чем я уверен, это плохо в синглетах, это когда мы их используем, игнорируя при этом хорошие практики. С этим действительно не так легко иметь дело. Но плохие практики могут быть применены к любому шаблону. И, я знаю, это слишком обобщенно, чтобы говорить это ... Я имею в виду, что это слишком много.
Не пойми меня неправильно!
Проще говоря, так же , как глобальные переменные , одиночка следует по- прежнему следуют избегать во все времена . Тем более, что ими злоупотребляют. Но глобальных изменений не всегда можно избежать, и мы должны использовать их в этом последнем случае.
Во всяком случае, есть много других предложений, кроме панели инструментов, и так же, как панель инструментов, у каждого есть свое приложение ...
Другие альтернативы
Лучшая статья , я только что прочитал о одиночках предлагает Service Locator в качестве альтернативы. Для меня это, по сути, « Static Toolbox », если хотите. Другими словами, сделайте Service Locator синглтоном, и у вас будет набор инструментов. Конечно, это идет вразрез с его первоначальным предложением избегать синглтона, но это только для того, чтобы навязать проблему синглтона в том, как он используется, а не в самом паттерне.
Другие предлагают Factory Pattern в качестве альтернативы. Это была первая альтернатива, которую я услышал от коллеги, и мы быстро исключили ее для использования в качестве глобальной переменной . Это конечно имеет свое использование, но так же как и синглтоны.
Обе альтернативы выше являются хорошими альтернативами. Но все зависит от вашего использования.
Теперь, намекая на синглетоны, следует избегать любой ценой, это просто неправильно ...
Неспособности (абстрактно или подкласс) действительно есть, но что с того? Это не для этого. Насколько я могу судить, нет возможности для интерфейса . Высокое сцепление также может быть там, но это только потому , что , как это обычно используется. Это не обязательно . Фактически, сама связь не имеет ничего общего с одноэлементной структурой. Это уточняется, но это также устраняет сложность тестирования. Что касается сложности распараллеливания, это зависит от языка и платформы, поэтому, опять же, не проблема для шаблона.
Практические примеры
Я часто вижу, как используются 2, как за, так и против синглетонов. Веб-кеш (мой случай) и служба журналов .
Регистрация, как утверждают некоторые , является идеальным примером синглтона, потому что, и я цитирую:
В то время как другие будут спорить, это затрудняет расширение службы журналов, как только вы в конечном итоге понимаете, что на самом деле это не должен быть только один экземпляр.
Ну, я говорю, что оба аргумента верны. Проблема здесь, опять же, не в синглтоне. Это зависит от архитектурных решений и веса, если рефакторинг представляет собой реальный риск. Это еще одна проблема, когда обычно рефакторинг является последней необходимой корректирующей мерой.
источник
java.awt.Toolkit
. Моя точка зрения такая же:Toolbox
звучит как мешок с не связанными кусочками, а не как связный класс с единственной целью. Не похоже на хороший дизайн для меня. (Обратите внимание, что статья, на которую вы ссылаетесь, относится к 2001 году, до того, как внедрение зависимостей и контейнеры DI стали обычным явлением.)Моя основная проблема с шаблоном одноэлементного проектирования заключается в том, что очень сложно написать хорошие модульные тесты для вашего приложения.
Каждый компонент, имеющий зависимость от этого «менеджера», делает это путем запроса своего экземпляра-одиночки. И если вы хотите написать модульный тест для такого компонента, вам нужно внедрить данные в этот одноэлементный экземпляр, что может быть непросто.
Если, с другой стороны, ваш «менеджер» внедряется в зависимые компоненты через параметр конструктора, и компонент не знает конкретный тип менеджера, только интерфейс или абстрактный базовый класс, который реализует менеджер, тогда модуль test может предоставить альтернативные реализации менеджера при тестировании зависимостей.
Если вы используете контейнеры IOC для настройки и создания экземпляров компонентов, составляющих ваше приложение, то вы можете легко настроить свой контейнер IOC для создания только одного экземпляра «менеджера», что позволит вам достичь того же самого, только одного экземпляра, управляющего глобальным кэшем приложения. ,
Но если вас не интересуют юнит-тесты, тогда шаблон одноэлементного дизайна вполне подойдет. (но я бы не стал это делать в любом случае)
источник
Синглтон не является фундаментально плохим , в том смысле, что все проектные вычисления могут быть хорошими или плохими. Это может быть только правильно (дает ожидаемые результаты) или нет. Это также может быть полезно или нет, если это делает код более понятным или более эффективным.
Один случай, когда синглтоны полезны, это когда они представляют сущность, которая действительно уникальна. В большинстве сред базы данных уникальны, в действительности существует только одна база данных. Подключение к этой базе данных может быть сложным, поскольку для этого требуются специальные разрешения или обход через несколько типов подключения. Организация этой связи в единый, вероятно, имеет большой смысл только по этой причине.
Но вы также должны быть уверены, что синглтон действительно является синглтоном, а не глобальной переменной. Это имеет значение, когда единственная уникальная база данных - это на самом деле 4 базы данных, по одной для производства, подготовки, разработки и тестирования. Синглтон базы данных выяснит, к какому из них он должен подключиться, возьмет один экземпляр для этой базы данных, подключит его при необходимости и вернет вызывающему.
Когда синглтон на самом деле не является синглтоном (это то, когда большинство программистов расстраиваются), это ленивый глобализированный экземпляр, нет возможности внедрить правильный экземпляр.
Еще одна полезная особенность хорошо спроектированного одноэлементного паттерна заключается в том, что он часто не наблюдается. Звонящий запрашивает соединение. Служба, которая предоставляет его, может возвращать объект из пула, или, если он выполняет тест, он может создать новый для каждого вызывающего или вместо этого предоставить фиктивный объект.
источник
Использование одноэлементного шаблона, который представляет реальные объекты, вполне приемлемо. Я пишу для iPhone, и в рамках Cocoa Touch есть много синглетонов. Само приложение представлено синглтоном класса
UIApplication
. Есть только одно приложение, которым вы являетесь, поэтому уместно представить это с помощью синглтона.Использование синглтона в качестве класса менеджера данных - это нормально, если оно правильно разработано. Если это набор свойств данных, это не лучше, чем глобальная область. Если это набор геттеров и сеттеров, это лучше, но все же не очень. Если это класс, который действительно управляет всем интерфейсом данных, включая, возможно, выборку удаленных данных, кэширование, настройку и разбор ... Это может быть очень полезно.
источник
Синглтоны - это просто проекция ориентированной на обслуживание архитектуры в программу.
API является примером синглтона на уровне протокола. Вы получаете доступ к Твиттеру, Google и т. Д. Через то, что по сути являются одиночками Так почему синглтоны становятся плохими в программе?
Это зависит от того, как вы думаете о программе. Если вы думаете о программе как о сообществе сервисов, а не как случайно привязанные кешированные экземпляры, тогда синглтоны имеют смысл.
Синглтоны - это точка доступа к сервису. Открытый интерфейс к тесно связанной библиотеке функциональности, которая скрывает, возможно, очень сложную внутреннюю архитектуру.
Так что я не вижу синглтона как отличного от заводского. В синглтоне могут передаваться параметры конструктора. Он может быть создан с помощью некоторого контекста, который знает, как, например, разрешить принтер по умолчанию для всех возможных механизмов выбора. Для тестирования вы можете вставить свой макет. Так что это может быть довольно гибким.
Ключ находится внутри программы, когда я выполняюсь и мне нужно немного функциональности, я могу получить доступ к одиночке с полной уверенностью, что сервис работает и готов к использованию. Это важно, когда в процессе запускаются разные потоки, которые должны пройти через конечный автомат, чтобы считаться готовыми.
Как правило, я бы обернул
XxxService
класс, который оборачивает один класс вокруг классаXxx
. Синглтон вообще не в классеXxx
, он разделен на другой классXxxService
. Это потому, чтоXxx
может иметь несколько экземпляров, хотя это маловероятно, но мы по-прежнему хотим, чтобы одинXxx
экземпляр был глобально доступен в каждой системе.XxxService
обеспечивает хорошее разделение проблем.Xxx
не требует принудительного применения одноэлементной политики, но мы можем использовать ееXxx
в качестве единственного, когда это необходимо.Что-то вроде:
источник
Первый вопрос, вы находите много ошибок в приложении? Может быть, вы забыли обновить кеш, или плохой кеш или его трудно изменить? (я помню, что приложение не изменило бы размеры, если вы не изменили цвет тоже ... однако вы можете изменить цвет обратно и сохранить размер).
То, что вы сделали бы, это иметь этот класс, но УДАЛИТЬ ВСЕ СТАТИЧЕСКИЕ ЧЛЕНЫ. Хорошо, это не обязательно, но я рекомендую это. На самом деле вы просто инициализируете класс как обычный класс и пропускаете указатель. Не говорите, что ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()
Это больше работы, но на самом деле, это менее запутанно. Некоторые места, где вы не должны менять то, что вы сейчас не можете, потому что это больше не глобально. Все мои классы менеджера являются обычными классами, просто относитесь к этому так.
источник
ИМО, твой пример звучит нормально. Я бы предложил вынести следующее: кешировать объект для каждого (и позади каждого) объекта данных; объекты кеша и объекты доступа db имеют одинаковый интерфейс. Это дает возможность обмениваться кешами внутри и снаружи кода; плюс это дает легкий путь расширения.
Графика:
Аксессор БД и кеш могут наследовать от одного и того же объекта или типа утки и выглядеть как один и тот же объект. Пока вы можете подключить / скомпилировать / проверить, и это все еще работает.
Это разъединяет вещи, так что вы можете добавлять новые кэши, не заходя и не изменяя некоторые объекты Uber-Cache. YMMV. IANAL. И Т.П.
источник
Немного опоздал на вечеринку, но все равно.
Singleton - это инструмент в наборе инструментов, как и все остальное. Надеюсь, у вас есть больше в вашем наборе инструментов, чем один молоток.
Учти это:
против
1-й случай приводит к высокому сцеплению и т. Д .; 2-й способ не имеет проблем @ Аарона описывает, насколько я могу судить. Это все о том, как вы используете это.
источник
Сделайте так, чтобы на каждом экране присутствовал менеджер в их конструкторе.
Когда вы запускаете свое приложение, вы создаете один экземпляр менеджера и передаете его.
Это называется инверсией управления и позволяет вам менять контроллер при изменении конфигурации и в тестах. Кроме того, вы можете запускать несколько экземпляров вашего приложения или его частей в параллельном режиме (хорошо для тестирования!). Наконец, ваш менеджер умрет со своим владельцем объекта (класс запуска).
Так что структурируйте свое приложение как дерево, где вещи выше владеют всем, что используется ниже них. Не реализуйте приложение как меш, где все знают всех и находят друг друга с помощью глобальных методов.
источник