Когда не следует использовать шаблон Singleton? (Кроме очевидного) - PullRequest
45 голосов
/ 02 ноября 2010

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

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

Банда четырех заявляет, что вы захотите использовать Синглтон, когда:

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

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

У кого-нибудь есть наборправил или предостережений, которые вы используете, чтобы оценить, действительно ли вы действительно уверены, что хотите использовать Singleton?

Ответы [ 8 ]

68 голосов
/ 02 ноября 2010

Сводная версия:

Вы знаете, как часто вы используете глобалы? Хорошо, теперь используйте Singletons даже меньше. Гораздо меньше на самом деле. Почти никогда. Они разделяют все проблемы, которые глобальные проблемы имеют со скрытой связью (напрямую влияющей на тестируемость и ремонтопригодность), и часто ограничение «только один может существовать» на самом деле является ошибочным предположением.

Подробный ответ:

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

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

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

При выпечке одиночного :: instance вызова, который также создает себя в классе, вы делаете наследование невозможным . Обходные пути обычно разрушают часть «одного экземпляра» одиночного файла. Рассмотрим ситуацию, когда у вас есть несколько проектов, использующих общий код в классе NetworkManager. Даже если вы хотите, чтобы этот NetworkManager был глобальным состоянием и единичным экземпляром, вы должны очень скептически относиться к превращению его в единый объект. Создавая простой синглтон, который создает себя, вы фактически лишаете возможности любого другого проекта наследовать этот класс.

Многие считают ServiceLocator анти-паттерном, однако я считаю, что он на полшага лучше, чем Singleton, и эффективно затмевает цель паттерна Go4. Существует много способов реализации локатора служб, но основная концепция заключается в том, что вы разбиваете конструкцию объекта и доступ к нему на два этапа. Таким образом, во время выполнения вы можете подключить соответствующую производную службу, а затем получить к ней доступ из единой глобальной точки контакта. Это имеет преимущество явного порядка построения объектов, а также позволяет вам выводить из вашего базового объекта. Это все еще плохо по большинству заявленных причин, но это на меньше плохо, чем синглтон и является заменой.

Один конкретный пример приемлемого синглтона (читай: servicelocator) может заключаться в обертывании интерфейса стиля c одного экземпляра, такого как SDL_mixer. Один пример синглтона, который часто наивно реализуется там, где его, вероятно, не должно быть, находится в классе журналирования (что происходит, когда вы хотите войти в консоль И на диск? Или если вы хотите регистрировать подсистемы отдельно.)

Однако наиболее важные проблемы, связанные с глобальным состоянием, всегда возникают, когда вы пытаетесь реализовать правильное модульное тестирование (и вы должны пытаться это сделать тот). С вашим приложением становится намного сложнее работать, когда недра классов, к которым вы на самом деле не имеете доступа, пытаются выполнять запись и чтение на жестком диске, подключаться к работающим серверам и отправлять реальные данные или воспроизводить звук из ваших динамиков. Вилли Нилли. НАМНОГО, гораздо лучше использовать внедрение зависимостей, чтобы вы могли смоделировать класс бездействия (и увидеть, что вам нужно сделать это в конструкторе классов) в случае плана тестирования и указать на это без необходимости гадать все глобальное состояние, от которого зависит ваш класс.

Ссылки по теме:

Использование шаблона против появления

Шаблоны полезны в качестве идей и терминов, но, к сожалению, люди, похоже, чувствуют необходимость «использовать» шаблон, когда действительно шаблоны реализуются в соответствии с потребностями. Часто синглтон специально включается просто потому, что это часто обсуждаемая модель. Проектируйте свою систему с учетом шаблонов, но не проектируйте свою систему специально для того, чтобы сгибаться к ним только потому, что они существуют. Это полезные концептуальные инструменты, но так же, как вы не используете каждый инструмент в наборе инструментов только потому, что можете, вы не должны делать то же самое с шаблонами. Используйте их по мере необходимости и не более или менее.

Пример сервисного локатора для одного экземпляра

#include <iostream>
#include <assert.h>

class Service {
public:
    static Service* Instance(){
        return _instance;
    }
    static Service* Connect(){
        assert(_instance == nullptr);
        _instance = new Service();
    }
    virtual ~Service(){}

    int GetData() const{
        return i;
    }
protected:
    Service(){}
    static Service* _instance;
    int i = 0;
};

class ServiceDerived : public Service {
public:
    static ServiceDerived* Instance(){
        return dynamic_cast<ServiceDerived*>(_instance);
    }
    static ServiceDerived* Connect(){
        assert(_instance == nullptr);
        _instance = new ServiceDerived();
    }
protected:
    ServiceDerived(){i = 10;}
};

Service* Service::_instance = nullptr;

int main() {
    //Swap which is Connected to test it out.
    Service::Connect();
    //ServiceDerived::Connect();
    std::cout << Service::Instance()->GetData() << "\n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1);
    return 0;
}
12 голосов
/ 02 ноября 2010

Одно слово: тестирование

Одним из отличительных признаков тестируемости является слабая связь классов, позволяющая вам изолировать один класс и полностью его протестировать. Когда один класс использует синглтон (а я говорю о классическом синглтоне, который реализует свою особенность посредством статического метода getInstance ()), пользователь синглтона и синглтон неразрывно связаны друг с другом. Больше невозможно тестировать пользователя без тестирования синглтона.

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

6 голосов
/ 02 ноября 2010

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

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

5 голосов
/ 02 ноября 2010

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

В конце концов, синглтон действует как узкое место ресурса, если спроектирован неправильно.

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

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

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

3 голосов
/ 02 ноября 2010

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

Конечным результатом является тарбол, который в итоге собирает все static во всей системе.Сколько людей здесь НИКОГДА не видели класс с именем Globals в каком-то якобы OOD-коде, с которым им приходилось работать?Тьфу.

0 голосов
/ 13 ноября 2010

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

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

Даже если мы отвергаем все, все еще остается вопрос тестируемости кода

0 голосов
/ 02 ноября 2010

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

Имейте в виду, этот шаблон проектирования является решением некоторых проблем, но не всех проблем.На самом деле, это одинаково для всех шаблонов дизайна.

0 голосов
/ 02 ноября 2010

См. Первый ответ в обсуждении " Что является альтернативой синглтону ".

...