I've been revising a lot of code the past week that belongs to a facade object, which I'll call "SuperConfiguration", that was implemented as a singleton, and I believe it has become a rather painful lesson in architectural design.
While I do not believe that singletons are evil, I just found out the hard way why one must be very, very careful when using them.
SuperConfiguration was written at the time when the design assumed that there was only one country being handled by the program at any given time. As such it was implemented as a singleton where all the related reference lookups to for a specified country were loaded to it as member objects. Processing only one country at a time, a singleton seemed sensible because every part of the program would only be needing one set of data at any thread. No problem there.
The crap hits the fan when the program started handling several countries at a time: a lot of "initializing code" needed to precede object instances that use the SuperConfiguration. That code usually makes sure that the SuperConfiguration singleton is using the same country as the object instance on the same thread.
Further exploring the domain objects, an ugly chain of dependencies surface:
- SuperConfiguration keeps members that are essentially ILists of domain objects
- IList of domain objects refer to a base domain object
- Base domain object refers to a UserConfiguration (also a singleton) that contains the current country being used
- Here's the painful part: UserConfiguration also refers to SuperConfiguration.
If anybody needs to circumvent circular dependency checking, singletons appear to be the way to go.
Unfortunately un-singletoning a class that is referred to by nearly each and every domain object isn't easy.
I've since removed the static declarations of the SuperConfiguration, therefore making it an instantiated object. 236 compile errors to go. Wish me luck.