|
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
|