Подтвердить что ты не робот

Когда следует использовать шаблон Singleton NOT? (Помимо очевидного)

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

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

The Gang of Four заявляет, что вы захотите использовать Singleton, когда:

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Есть тонкие проблемы с этим, однако, когда кажется, что вы не полагаетесь на глобальные данные, но вы. Так что (используя вложение зависимостей интерфейса, который обертывает синглтон), является лишь предложением, а не правилом. В общем, это все же лучше, потому что по крайней мере вы можете видеть, что класс полагается на singleton, тогда как просто использование функции:: instance() внутри живота функции члена класса скрывает эту зависимость. Он также позволяет извлекать классы, основанные на глобальном состоянии, и делать для них более эффективные модульные тесты, и вы можете пройти в mock-do-nothing объектах, где, если вы испепеляете зависимость от синглтона непосредственно в классе, это намного сложнее.

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

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

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

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

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

Использование шаблона vs Emergence

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

Пример одиночного экземпляра Service Locator

#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;
}

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

Ответ 6

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

Помните, что эта схема проектирования - это решение для решения некоторых проблем, но не для всех проблем. На самом деле, это то же самое для всех шаблонов проектирования.

Ответ 7

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

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

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