Какая альтернатива синглтону

114

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

Без синглтона мы должны передавать экземпляр повсюду в нашем коде. Становится так запутанно, что мы написали синглтон-оболочку. Теперь мы переносим тот же код на PHP и .NET, и мне интересно, есть ли лучший шаблон, который мы можем использовать для объекта конфигурации.

Джон Милл
источник

Ответы:

131

В блоге Google Testing есть серия статей об отказе от синглтона (для создания тестируемого кода). Может быть, это поможет вам:

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

Короче говоря, мы переводим всех новых операторов на завод. Мы группируем все объекты с одинаковым сроком службы в одну фабрику.

FrankS
источник
3
*** Использование инъекции зависимости для предотвращения одиночных запросов
Джастин,
Эти статьи не уступают стандартам программирования Google C ++!
2
Ну не совсем. Совет «Не использовать статические методы» прямо противоречит принципу минимального интерфейса Скотта Мейерса / Херба Саттерса. Есть полезные советы, но в них не хватает вклада нескольких умов.
Matthieu M.
@FrankS, почему вы изменили порядок ссылок? Сначала это было в хорошем хронологическом порядке.
cregox
@Cawas на самом деле понятия не имею, это было больше четырех лет назад, так что я думаю, у меня были некоторые причины для этого тогда :-)
FrankS
15

Лучше всего использовать вместо этого шаблон Factory. Когда вы создаете новый экземпляр своего класса (в фабрике), вы можете вставить «глобальные» данные во вновь созданный объект либо как ссылку на единственный экземпляр (который вы храните в фабричном классе), либо скопировав соответствующий данные в новый объект.

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

gbjbaanb
источник
1
Я не согласен с утверждением «лучший способ», но +1 за хорошую альтернативу.
Tylermac
Проблема с этим подходом заключается в том, что каждый новый объект содержит (или ссылается) на то, что потенциально может быть огромным объемом данных. var_dump () для любого из этих объектов, содержащих куски, очень быстро приводит к огромным спискам, свободно усыпанным предупреждениями о рекурсии . Это чертовски уродливо, не может быть очень эффективным и заставляет вещи казаться ненормальными. Однако я лично не нашел лучшего способа. Я изменил "фабричный" метод на использование __construct () для ссылки на глобал. Однако кажется, что все перевернулось назад, чтобы избежать ужасного синглтона ...
FYA
2
@EastGhostCom: с таким же успехом мы могли бы использовать синглтон и перестать усложнять себе жизнь :)
gbjbaanb
5

Я мог бы сказать очевидное здесь, но есть ли причина, по которой вы не можете использовать структуру внедрения зависимостей, такую ​​как Spring или Guice ? (Я считаю, что Spring теперь также доступен для .NET).

Таким образом, фреймворк может содержать единственную копию объектов конфигурации, и вашим bean-компонентам (службам, DAO и т. Д.) Не нужно беспокоиться о поиске их.

Я обычно придерживаюсь этого подхода!


источник
4

Если вы используете Spring Framework , вы можете просто создать обычный компонент. По умолчанию (или если вы явно задали scope="singleton") создается только один экземпляр bean-компонента, и этот экземпляр возвращается каждый раз, когда bean-компонент используется в зависимости или извлекается через getBean().

Вы получаете преимущество единственного экземпляра без привязки к шаблону Singleton.

Джин Готимер
источник
3
О, ирония - используйте (синглтон) Spring beans для замены своего синглтона ...
Зак Макомбер,
4

Альтернатива - передать то, что вам нужно, вместо того, чтобы просить объект о вещах.

Koen
источник
4

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

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

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

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

следует отремонтировать на:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

рамки инъекции зависимостей поможет много здесь, но это не stricly требуется.

DFA
источник
Да, это действительно хороший аргумент. Я делал это раньше. Мой большой объект конфигурации реализовывал такие интерфейсы, как MailServiceConf, ServerConf .., чем структура внедрения зависимостей передает конфигурацию классам, поэтому мои классы не зависят от большого объекта конфигурации.
Caltuntas
1

Вы можете добиться того же поведения синглтона, используя статические методы. Стив Йегге очень хорошо объясняет это в этом посте.

Юсеф
источник
На самом деле статья неплохая, и в ней не говорится, что вы должны использовать вместо этого статические методы. Вместо этого он заявляет, что статические методы - это тоже синглтоны, и в конце он рекомендует использовать шаблон фабричного метода: «В заключение я скажу, что если вы все еще чувствуете необходимость использовать одиночные объекты, подумайте об использовании вместо этого шаблона фабричного метода. ... »
FrankS
0

Возможен ли класс, содержащий только статические методы и поля? Я не совсем уверен в вашей ситуации, но, возможно, стоит изучить ее.

Томас Оуэнс
источник
1
Если класс не имеет состояния, это должен быть статический класс.
AlbertoPL
1
Это в C ++ - шаблон известен как Monostate.
0

Зависит от того, какие инструменты / фреймворки и т. Д. Используются. С помощью инструментов внедрения зависимостей / ioc можно часто по-прежнему получить производительность / оптимизацию одноэлементного объекта, если контейнер di / ioc использует одноэлементное поведение для требуемого класса (например, интерфейс IConfigSettings), создав только один экземпляр класса. Это все еще можно заменить для тестирования

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

сарет
источник
0

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

MyReuseCode.Configure(IConfiguration)

Код инициализации системы будет выглядеть:

Library.init(MyIConfigurationImpl)
Dewfy
источник
0

Вы можете использовать среду внедрения зависимостей, чтобы облегчить боль при передаче объекта конфигурации. Достойным является ninject , преимущество которого заключается в использовании кода, а не xml.

Колин Гравилл
источник
0

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

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

вы можете вызвать его createSingleton(Information info)непосредственно при запуске приложения (и в методах setUp модульных тестов).

Роланд Эвальд
источник
-1

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

DI с использованием Spring и т. Д. - очень хороший вариант, но не единственный.

кодовая матрица
источник