Здесь важно различать отдельные экземпляры и шаблон проектирования Singleton .
Отдельные случаи просто реальность. Большинство приложений предназначены для работы только с одной конфигурацией за раз, с одним пользовательским интерфейсом за раз, с одной файловой системой за раз и так далее. Если нужно сохранить много состояний или данных, то, безусловно, вы захотите иметь только один экземпляр и поддерживать его как можно дольше.
Шаблон проектирования Singleton - это очень специфический тип отдельного экземпляра, а именно:
- Доступно через глобальное статическое поле экземпляра;
- Создается либо при инициализации программы, либо при первом доступе;
- Нет открытого конструктора (не может быть создан непосредственно);
- Никогда не освобождается явно (неявно освобождается при завершении программы).
Именно из-за этого конкретного выбора дизайна шаблон представляет несколько потенциальных долгосрочных проблем:
- Невозможность использования абстрактных или интерфейсных классов;
- Невозможность подкласса;
- Высокая связь по приложению (трудно изменить);
- Трудно проверить (не может подделать / издеваться в юнит-тестах);
- Сложно распараллелить в случае изменяемого состояния (требуется обширная блокировка);
- и так далее.
Ни один из этих симптомов на самом деле не является эндемичным для единичных случаев, только паттерн Синглтона.
Что вы можете сделать вместо этого? Просто не используйте шаблон Singleton.
Цитирую из вопроса:
Идея заключалась в том, чтобы иметь это единственное место в приложении, которое хранит и синхронизирует данные, и тогда любые новые открытые экраны могут просто запрашивать большую часть того, что им нужно оттуда, без повторяющихся запросов различных вспомогательных данных с сервера. Постоянный запрос к серверу занимал бы слишком большую пропускную способность - и я говорю о дополнительных тысячах интернет-счетов в неделю, так что это было неприемлемо.
У этой концепции есть название, как вы намекаете, но звучите неуверенно. Это называется кеш . Если вы хотите стать модным, вы можете назвать его «автономным кэшем» или просто автономной копией удаленных данных.
Кэш не должен быть одиночным. Это может потребоваться для одного экземпляра, если вы хотите избежать выборки одних и тех же данных для нескольких экземпляров кэша; но это не значит, что на самом деле вы должны всем разоблачать .
Первое, что я хотел бы сделать, это разделить различные функциональные области кэша на отдельные интерфейсы. Например, допустим, вы создали худший в мире клон YouTube на основе Microsoft Access:
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Здесь у вас есть несколько интерфейсов, описывающих конкретные типы данных, к которым конкретному классу может понадобиться доступ - медиа, профили пользователей и статические страницы (например, первая страница). Все это реализовано одним мега-кешем, но вы разрабатываете свои индивидуальные классы так, чтобы вместо них принимались интерфейсы, поэтому им все равно, какой у них экземпляр. Вы инициализируете физический экземпляр один раз, когда запускается ваша программа, а затем просто начинаете передавать экземпляры (приведенные к определенному типу интерфейса) через конструкторы и открытые свойства.
Кстати, это называется инъекция зависимости ; вам не нужно использовать Spring или какой-либо специальный IoC-контейнер, только если ваш общий дизайн класса принимает его зависимости от вызывающей стороны вместо того, чтобы создавать их экземпляры самостоятельно или ссылаться на глобальное состояние .
Почему вы должны использовать дизайн на основе интерфейса? Три причины:
Это облегчает чтение кода; из интерфейсов можно четко понять, от каких данных зависят зависимые классы.
Если и когда вы поймете, что Microsoft Access не был лучшим выбором для серверной части данных, вы можете заменить ее на что-то лучшее - скажем, на SQL Server.
Если и когда вы понимаете , что SQL Server не является лучшим выбором для средств массовой информации специально , вы можете разбить реализацию , не затрагивая другие части системы . Вот где приходит настоящая сила абстракции.
Если вы хотите сделать еще один шаг вперед, вы можете использовать IoC-контейнер (DI-фреймворк), например Spring (Java) или Unity (.NET). Почти каждая инфраструктура DI будет осуществлять свое собственное управление временем жизни и, в частности, позволит вам определить конкретную услугу как отдельный экземпляр (часто его называют «одиночным», но это только для ознакомления). По сути, эти фреймворки избавляют вас от лишней ручной обработки экземпляров, но они не являются строго необходимыми. Вам не нужно никаких специальных инструментов для реализации этого дизайна.
Для полноты картины я должен отметить, что приведенный выше дизайн тоже не идеален. Когда вы имеете дело с кешем (как и вы), у вас фактически должен быть совершенно отдельный слой . Другими словами, такой дизайн:
+ - IMediaRepository
|
Кэш (общий) --------------- + - IProfileRepository
▲ |
| + - IPageRepository
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Преимущество этого в том, что вам даже не нужно разбивать свой Cache
экземпляр, если вы решите провести рефакторинг; Вы можете изменить способ хранения медиа, просто добавив альтернативную реализацию IMediaRepository
. Если вы подумаете о том, как это согласуется, вы увидите, что он все еще создает только один физический экземпляр кэша, поэтому вам никогда не придется извлекать одни и те же данные дважды.
Ничто из этого не говорит о том, что каждый кусочек программного обеспечения в мире должен быть спроектирован в соответствии с этими строгими стандартами высокой когезии и слабой связи; это зависит от размера и масштаба проекта, вашей команды, вашего бюджета, сроков и т. д. Но если вы спрашиваете, какой дизайн лучше (использовать вместо одиночного), то это он.
PS Как уже говорили другие, для зависимых классов, вероятно, не самая лучшая идея знать, что они используют кеш - это деталь реализации, о которой им просто не следует беспокоиться. При этом общая архитектура все равно будет выглядеть очень похоже на то, что изображено выше, вы просто не будете ссылаться на отдельные интерфейсы как на кеши . Вместо этого вы бы назвали их Услуги или что-то подобное.