Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Как я боролся с Singleton-ами

3
03 декабря 2007 года
Green
4.8K / / 20.01.2000
Представьте себе систему состоящую из множества подсистем.
Т.к. вся система изначально могла в приложении существовать только в единственном экземпляре, то сама она и многие её подсистемы были сделаны синглтонами ("одиночками"), кроме того в системе существует множество глобальных объектов.
За время своего существования (около 10 лет) система обросла множеством таких подсистем и глобальных объектов. И вот в один прекрасный момент понадобилось создать несколько таких систем в одном приложении. Вот тогда и почувствовалось всё зло от глобальных данных и синглтонов.

Последовательный рефакторинг (избавление от "одиночества") всех подсистем оказался очень трудоемким, в частности из-за большой связанности подсистем: приходилось протаскивать ссылки на соседние подсистемы по всей иерархии вызовов и/или хранить множество перекрестных ссылок.

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

Как это выглядит в коде на упрощенном примере.

До применения решения была система, которая инициализировала вначале свои подсистемы и глобальные объекты:
Код:
class System :public Singleton<System>
{
public:
    static bool init()
    {
        bool result = true;
        result &= Subsystem1::init();
        result &= Subsystem2::init();
        .....
        result &= SubsystemN::init();

        result &= globalObject1.init();
        result &= globalObject2.set(value);

        return result;
    }
};

SomeClass1 globalObject1;
SomeClass2 globalObject2;
int globalObject3 = 0;
.....

Каждая подсистема, как уже говорилось, так же является синглтоном:
Код:
template<class T>
class Singleton
{
public:
    T& bool get() {
        if(pInst == 0) {
            pInst = new T;
        }
        return *pInst;
    }

private:
    T* pInst;
};

class SubsystemN :public Singleton<SubsystemN>
{
    static bool init() {
        Singleton<SubsystemN>::get();
        .....
        return result;
    }
};
3
03 декабря 2007 года
Green
4.8K / / 20.01.2000
Теперь, само решение

Класс SingletonsContext представляет собой контекст в котором работает система. Для того, чтоб не тащить в заголовочном файле этого класса ссылки на заголовочные файлы классов подсистем (принцип Парнаса), класс разбит на интерфейсную (class SingletonsContext) часть и реализацию (class SingletonsContextData):
Код:
// SingletonsContext.h

SingletonsContextData;

class SingletonsContext
{
public:
    SingletonsContext();
    ~SingletonsContext();

    void save();    
    void set();    

private:
    SingletonsContextData* data;
}

Код:
// SingletonsContext.cpp
#include "Subsystem1.h"
#include "Subsystem2.h"
.....
#include "SubsystemN.h"

extern SomeClass1 globalObject1;
extern SomeClass2 globalObject2;
extern int globalObject3;

///////////////////////////////////////
// class SingletonsContextData
//
class SingletonsContextData
{
public:
    SingletonsContextData();

    SingletonsContextData& save();
    void set();

private:
    Subsystem1::pInst savedSubsystem1;
    Subsystem2::pInst savedSubsystem2;
    .....
    SubsystemN::pInst savedSubsystemN;

    SomeClass1 savedGlobalObject1;
    SomeClass2 savedGlobalObject2;
    int savedGlobalObject3;
};


static SingletonsContextData zeroSingletonsContextData = SingletonsContextData().save();


SingletonsContextData::SingletonsContextData()
{
    *this = zeroSingletonsContextData;
}

SingletonsContextData& SingletonsContextData::save()
{
    savedSubsystem1 = Subsystem1::pInst;
    savedSubsystem2 = Subsystem2::pInst;
    .....
    savedSubsystemN = SubsystemN::pInst;

    savedGlobalObject1 = globalObject1;
    savedGlobalObject2 = globalObject2;
    savedGlobalObject3 = globalObject3;
    .....

    return *this;
}

SingletonsContextData& SingletonsContextData::set()
{
    Subsystem1::pInst = subsystem1;
    Subsystem2::pInst = subsystem2;
    .....
    SubsystemN::pInst = subsystemN;

    globalObject1 = savedGlobalObject1;
    globalObject2 = savedGlobalObject2;
    globalObject3 = savedGlobalObject3;
    .....
}


///////////////////////////////////////
// class SingletonsContext
//
SingletonsContext::SingletonsContext()
{
    data = new SingletonsContextData;
}

SingletonsContext::~SingletonsContext()
{
    delete data;
}

void SingletonsContext::save()
{
    data->save();
}

void SingletonsContext::set()
{
    data->set();
}

Метод save сохраняет состояние (конекст) системы, а метод set восстанавливает его.

Поясню, для чего нужен локальный объект zeroSingletonsContextData.
Дело в том, что, как уже говорил, система кроме набора синглтонов имеет некоторые глобальные объекты, которые инициализированы уже при старте программы (как например globalObject3). Т.о. система имеет некоторое начальное состояние при запуске. Это начальное состояние необходимо сохранить, а потом восстанавливать при запуске каждого экземпляра системы (см. конструктор класса SingletonsContextData). Для сохранения такого начального состояния и служит объект zeroSingletonsContextData. Чтобы создать этот объект при инициализации программы, метод SingletonsContextData::save() возвращает ссылку на экземпляр (см. инициализацию zeroSingletonsContextData).

Т.о. единственное изменение старого кода заключается в объявлении класса SingletonsContextData другом класса Singleton для доступа к pInst:
Код:
template<class T>
class Singleton
{
    friend class SingletonsContextData;

public:
    T& bool get() {
        if(pInst == 0) {
            pInst = new T;
        }
        return *pInst;
    }

private:
    T* pInst;
};
3
03 декабря 2007 года
Green
4.8K / / 20.01.2000
Далее для удобства работы был создан вспомогательный класс SingletonsContextChanger:
Код:
class SingletonsContextChanger
{
public:
    SingletonsContextChanger(SingletonsContext& newContext);
    ~SingletonsContextChanger();

private:
    SingletonsContextChanger();

    SingletonsContext& newContext;
    SingletonsContext* prevContext;

    static SingletonsContext*  currentContext;
};


SingletonsContextChanger::SingletonsContextChanger(SingletonsContext& newContext)
: newContext(newContext)
{
    prevContext = currentContext;
    if(currentContext != &newContext) {
        newContext.set();
        currentContext = &newContext;
    }
}

SingletonsContextChanger::~SingletonsContextChanger()
{
    if(currentContext != prevContext) {
        newContext.save();
        if(prevContext != 0) {
            prevContext->set();
        }
    }
    currentContext = prevContext;
}

SingletonsContext* SingletonsContextChanger::currentContext = 0;

Задача класса - упростить и оптимизировать (не включать контекст, если он уже включен) переключение контекстов с использованием класса SingletonsContext. Собственно, переключение происходит в конструкторе и деструкторе класса SingletonsContextCanger.
3
03 декабря 2007 года
Green
4.8K / / 20.01.2000
Теперь, как это применяется

Класс System, который является синглтоном, оборачивается в класс SystemInstance, который синглтоном не является. Здесь же хранится контекст работы синглтонов:
 
Код:
class SystemInstance
{
public:
    bool init();

private:
    System system;
    SingletonsContext context;
}

В начале каждого метода класса SystemInstance происходит переключение на соотв. контекст синглтонов при помощи создания локального объекта класса SingletonsContextChanger. А далее работа происходит по обычной схеме, как в то далекое время, когда синглтоны правили миром:
 
Код:
bool SystemInstance::init()
{
    SingletonsContextChanger changer(context);

    return system.init();
}


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

P.S. Забавно, что по сути решение заменяет множество синглтонов и глобальных объектов одним - SingletonsContextChanger::currentContext, т.о. никуда они не деваются, а, просто, "встают на одну ногу". :)
2
03 декабря 2007 года
squirL
5.6K / / 13.08.2003
прекрасно. только лучше это было в codenet community на ЖЖ выложить :)
5
05 декабря 2007 года
hardcase
4.5K / / 09.08.2005
Интересный опыт.
Чем-то напомнило домены приложений в .NET с их контекстами.
11K
05 декабря 2007 года
Free Thinker
118 / / 16.03.2007
Познавательно :)
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог