Singletons testing and subclassing
I saw this while using picocontainer. They say you have to avoid singletons. because the Singleton pattern makes it almost impossible for the class (and possibly all other classes which depend on it) to be testable. It's very hard to subclass, or to crea开发者_如何学JAVAte a mock object for a Singleton class.
But if you absolutely need it , Is there a workaround for the testing and subclassing issue ?
What makes it hard to test singletons is the code enforcing their singleton-ness (meaning the public static MySingleton getInstance() {...}
boilerplate). Using an inversion-of-control container, like Picocontainer or Guice or Spring, removes that concern from the object, so now:
It can be instantiated and have collaborators plugged into it in tests without a problem.
The code calling a singleton doesn't have to know what class it is looking up (which it would need to know if it had to call a static method).
I interpret the advice on picocontainer's website as being similar to this. What they are telling you is, let our container manage the scope of your components for you, don't hard-wire the scope-enforcing code into them.
If you must have singletons:
- Have an interface describing each singleton
- Access your singletons from a global/singleton ServiceLocator
- Switch the instances registered in the ServiceLocator during testing
Example:
interface IBankApi
{
public void MakeDeposity(int accountNumber, int dollarAmount);
// ...
}
public class RealBankApi : IBankApi { ... }
// startup code
serviceLocator.Register<IBankApi>(new RealBankApi());
// code using the API
serviceLocator.Resolve<IBankApi>().MakeDeposit(...);
// test code setup
class FakeBankApi : IBankApi { ... }
serviceLocator.Register<IBankApi>(new FakeBankApi());
Using IOC (inversion of control) rather than singletons initiated at first use is advantageous for other reasons too.
Singleton-initialisation can suffer (famously) from multi-threading issues where two threads try to access it for the first time simultaneously. Subsequent access is likely to be correctly synchonised but the first one is much harder to do.
Another huge advantage I have found of using IOC is when an error may occur in initialisation. You do not want this to happen on "first use", you want to know of this failure at an early stage, and certainly it is easier to handle the error this way.
Finally, with regards to testing, IOC provides the perfect model to isolate components, substitute them as necessary and bring together different combinations in a more flexible way, thus providing the perfect harness for both unit-testing and integration testing, as well as a good rollback mechanism without actually having to revert any code at all.
The general reason singletons are so often used is not for the one-ness but for the global-ness. If your project is managed correctly you have a single global object to which all the others "register" (thus your IOC model hangs off it) and are available globally whilst still being configurable.
精彩评论