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;
.....
Как я боролся с Singleton-ами
Т.к. вся система изначально могла в приложении существовать только в единственном экземпляре, то сама она и многие её подсистемы были сделаны синглтонами ("одиночками"), кроме того в системе существует множество глобальных объектов.
За время своего существования (около 10 лет) система обросла множеством таких подсистем и глобальных объектов. И вот в один прекрасный момент понадобилось создать несколько таких систем в одном приложении. Вот тогда и почувствовалось всё зло от глобальных данных и синглтонов.
Последовательный рефакторинг (избавление от "одиночества") всех подсистем оказался очень трудоемким, в частности из-за большой связанности подсистем: приходилось протаскивать ссылки на соседние подсистемы по всей иерархии вызовов и/или хранить множество перекрестных ссылок.
Для скорости разработки (получения работающего примера) и минимального внесения изменений в старый код (который продолжает использоваться на других проектах) реализовал следущее решение: создать контекст синглтонов и переключать его по мере надобности.
Как это выглядит в коде на упрощенном примере.
До применения решения была система, которая инициализировала вначале свои подсистемы и глобальные объекты:
Код:
Каждая подсистема, как уже говорилось, так же является синглтоном:
Код:
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;
}
};
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;
}
};
Класс SingletonsContext представляет собой контекст в котором работает система. Для того, чтоб не тащить в заголовочном файле этого класса ссылки на заголовочные файлы классов подсистем (принцип Парнаса), класс разбит на интерфейсную (class SingletonsContext) часть и реализацию (class SingletonsContextData):
Код:
// SingletonsContext.h
SingletonsContextData;
class SingletonsContext
{
public:
SingletonsContext();
~SingletonsContext();
void save();
void set();
private:
SingletonsContextData* data;
}
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();
}
#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;
};
class Singleton
{
friend class SingletonsContextData;
public:
T& bool get() {
if(pInst == 0) {
pInst = new T;
}
return *pInst;
}
private:
T* pInst;
};
Код:
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;
{
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.
Класс System, который является синглтоном, оборачивается в класс SystemInstance, который синглтоном не является. Здесь же хранится контекст работы синглтонов:
Код:
class SystemInstance
{
public:
bool init();
private:
System system;
SingletonsContext context;
}
{
public:
bool init();
private:
System system;
SingletonsContext context;
}
В начале каждого метода класса SystemInstance происходит переключение на соотв. контекст синглтонов при помощи создания локального объекта класса SingletonsContextChanger. А далее работа происходит по обычной схеме, как в то далекое время, когда синглтоны правили миром:
Код:
bool SystemInstance::init()
{
SingletonsContextChanger changer(context);
return system.init();
}
{
SingletonsContextChanger changer(context);
return system.init();
}
Вот такая техника. Может, кому пригодиться.
Конечно, правильным решением будет рефакторинг системы с целью удаления синглтонов, но как быстрое решение или временное решение такая техника себя оправдала.
P.S. Забавно, что по сути решение заменяет множество синглтонов и глобальных объектов одним - SingletonsContextChanger::currentContext, т.о. никуда они не деваются, а, просто, "встают на одну ногу". :)
прекрасно. только лучше это было в codenet community на ЖЖ выложить :)
Чем-то напомнило домены приложений в .NET с их контекстами.
Познавательно :)