class Singleton{public:Singleton& Instance();void DoSomething();privateSingleton *_instance;}void func(){Singleton::Instance().DoSomething();}
For one thing, what about threading? What if two threads instantiate the Singleton at the same time? Well, you could use a critical section, mutex or semaphore to serialize access. Problem solved? Yes, but still the object is created and when does it call its destructor? If I look above, the object is instantiated but not freed until the application terminates. It also allows any class to access this singleton without restriction; its methods are public. The truth is that the life-cycle as well as access is not clear, not tight, not proper OOP.
OOP methodology calls for something better. Objects must maintain tight scope; meaning they should instantiate and destruct in a predictable order. Singletons violate this principle, by letting any class instantiate and access the public methods, on demand. This can lead to difficult-to-find bugs.
There is another more OOP acceptable solution: The collaborator pattern. In this, the order of access is tightly controlled. The collaborator pattern gives a hierarchy of control to each class. For example: Instead of a singleton, we simply make singleton a friend of a parent class, which we will call Application. Application then contains a function to return the current instance of the singleton. This works on down the line, from parent to child. Each class is then assigned a responsible parent class.
class FakeSingleton
{
friend Application;
public:
DoSomething();
private:
FakeSingleton();
}
class Application
{
public:
FakeSingleton &fakeResource()
{
if(_fakeSingleton==null)
_fakeSingleton = new FakeSingleton();
return *_fakeSingleton;
}
~Application() {
if(_fakeSingleton !=null)
delete _fakeSingleton;
}
private:
FakeSingleton *_fakeSingleton;
}
Another variation is to not return an instance, but control access through wrapper functions.
This pattern therefore allows us to determine the sequence of initialization, as well as maximum lifetime. But what if we want to free the resource upon last use? A factory class can be useful in this case. A factory class, wraps the collaborator object. When this wrapper destructs, it frees the collaborator objects when the usage count goes to zero.
void Func(){// _fakeSingleton count incremented.FakeSingletonFactory fakeSingleton();fakeSingleton->DoSomething();// _fakeSingleton count decremented, possibly freed.}
Another nifty trick is the idea of the stack-only class:
class A{private:void * operator new(size_t size) {}};int main(){A a;A * b;A * c = new A;}
With this class, we are able to wrap our object in a stack variable.
I will post a full example on CodeProject, showing different ways the collaborator pattern can be used in C++, C# and PHP.
No comments:
Post a Comment