CTO does lazy load. Film at 11.
Sometimes a CTO needs to get back to coding. Here's something that came up in an internal discussion.
Suppose we have this code:
public class HeavyFoo {
public HeavyFoo() {
// big honking constructor
}
public void DoSomething() {
}
}
public class UserOfHeavyFoo {
private HeavyFoo heavyFoo;
public UserOfHeavyFoo() {
// don't want to create heavyFoo here
}
public void UseHeavyFoo() {
if (heavyFoo == null)
heavyFoo = new HeavyFoo();
heavyFoo.DoSomething();
}
}
We imagine we have this useful class called HeavyFoo.
Unfortunately its constructor does some heavy processing, maybe by
setting up some connection to somewhere, setting up a bunch of data,
reading a file, or whatever. So we'd rather create a
HeavyFoo object only when we really, really need it. This
is exactly what the UserOfHeavyFoo.UseHeavyFoo() method
does: if the internal field hasn't been initialized yet, go ahead and
create the HeavyFoo instance. This pattern is known as
the lazy load pattern.
All well and good, but it imposes a hidden contract on the developer
of the UserOfHeavyFoo class: you can never "just" refer
to the heavyFoo field, you always have to make sure that
it's initialized beforehand.
So people traditionally refactor this code to something more like this, to use a read-only property:
public class UserOfHeavyFoo {
private HeavyFoo heavyFoo;
private HeavyFoo HeavyFoo {
get {
if (heavyFoo == null)
heavyFoo = new HeavyFoo();
return heavyFoo;
}
}
public UserOfHeavyFoo() {
// don't want to create heavyFoo here
}
public void UseHeavyFoo() {
HeavyFoo.DoSomething();
}
}
At a superficial level this seems to help, but we still have a hidden
contract: when we write a new method, we should never use the
heavyFoo field and always use the HeavyFoo
property. If we don't, the code will still compile, but it may fail in
weird ways at run-time depending on the order of execution of our new
method vs. UseHeavyFoo(). Call the
UseHeavyFoo() method first and everything works, call the
new method first and you'll get a crash.
What we'd like to do is to make sure that we can't access the bare
heavyFoo field. The only way to do that is to take it out
of the UserOfHeavyFoo class and put it somewhere else. My
current idea is along these lines:
public class LazyLoad<T> where T : class, new() {
private T item;
public T Item {
get {
if (item == null)
item = new T();
return item;
}
}
}
public class UserOfHeavyFoo {
private LazyLoad<HeavyFoo> heavyFoo;
public UserOfHeavyFoo() {
heavyFoo = new LazyLoad<HeavyFoo>();
}
public void UseHeavyFoo() {
heavyFoo.Item.DoSomething();
}
}
This is much better in one way since we can no longer refer to an
uninitialized HeavyFoo instance in our
UserOfHeavyFoo class, but it looks a little awkward in
another: we now have a double dereference when we want to do
something. Also, the LazyLoad generic class can only deal
with classes that have a constructor with no parameters; something I
think is unlikely for heavy duty constructors. We can avoid both of
these by having a specific lazy load class for HeavyFoo,
but I can imagine that if we were to have a series of these lazy load
classes, there would be a lot of code and behavior duplication.
So I'm still thinking about this one, although I'm beginning to think
that HeavyFoo has something to do with the code smell.