From: Steve F. <st...@m3...> - 2002-06-29 16:02:55
|
One of the points of the original paper is that the need to substitute mock implementations pushes you to pass objects around a lot more than most of us have been used to -- a "compositional" style of programming. This can force you into having lots of parameters in a signature, but that's usually a sign that there's an intermediate object waiting for you to group some behaviour together. Sometimes this means passing in factories when constructing an object, and the one concession to testing we sometimes make is to have two constructors: one which allows you to set various instance fields (so you can substitute some mocks), and one which sets up those fields with default implementations, e.g. class ThingDoer { private FooFactory fooFactory; public ThingDoer(FooFactory aFooFactory) { fooFactory = aFooFactory; } public ThingDoer() { this(new HttpFooFactory()); } - - - - Concerning the original question, I'm currently doing quite a lot of test retrofitting. One of the tricks for dealing with singletons is to declare an interface, attach it to the singleton object, and pass it in to the calling object. e.g. class Notifier {... class ThingDoer { public void makeItSo() { doStuff(); Notifier.tellTheWorld("we did it"); } } becomes class AsynchNotifier implements Notifier {... class ThingDoer { private Notifier notifier; public ThingDoer(Notifier aNotifier) { notifier = aNotifier; } public void makeItSo() { doStuff(); notifier.tellTheWorld("we did it"); } // or, alternatively public void makeItSo(Notifier notifier) { doStuff(); notifier.tellTheWorld("we did it"); } You can now test the use of the notifier by passing a mock implementation somewhere and, quite often, it turns out that it shouldn't have been a singleton after all. b.t.w., there's a SingletonsAreEvil page on the C2 wiki. Steve |