An Interesting Refactoring (Wonkish)

I’ve been working on an interesting refactoring of Tinderbox Six this week, prompted by a simple study of which Tinderbox classes depend on which other classes.

Tons of classes depends on Node, the base class for notes, adornments, and other Tinderbox items. Many classes depend on Hypertext, the base class for Tinderbox documents. Node had also grown absurdly large, with something like 270 public methods. This is bad form. I’ve been working to break up Node for years, and most of the real work today is done by specialized classes like Content; most of the 270 methods are simply forwarding messages.

Still: 270 methods!

On the other hand, Tinderbox is about making, analyzing, and sharing notes. It’s not entirely surprising that lots of Tinderbox classes need to know about notes. And each dependent class seems to need a different subset of those methods, so there aren’t lots of great opportunities to solve the problem with Extract Interface.

This has been a headache for a long time. Changing Node or Hypertext means recompiling the world, pretty much; in the Metrowerks compiler, which we used for Tinderbox 1-4, this could take half an hour. Even in Xcode, recompilation can be a deterrent.

Refactoring toward Decorator seems a natural fit. We could have a concrete class, BasicNode with the bare essentials, and decorate it with drawing behaviors, text behaviors, I/O behaviors. But refactoring toward Decorator is notoriously hard, and a 270-method decorator is absurd.

I believe I’ve found a decent refactoring path. I think it’s fairly well known, but I haven’t seen it named before, and I don’t believe it’s in Fowler or Feathers or Kerievsky. So I’m going to call it Extract Satellite until someone writes to tell me what everyone else calls it. (Email me.)

What we do is define a new class — let’s say NodeText, that simply wraps a reference to a Node and has no other data.

class NodeText{ public: NodeText(Node* inNode):node(inNode){} ~NodeText(){} private: Node* node; };

Next, we move some public methods for text handling from Node to NodeText. We can move related private methods as well. Classes that use those methods now depend both on Node and on NodeText; they make a new NodeText on the stack whenever they need it.

NodeText worker(node); worker.SetText("…");

The new NodeText class now has all the interesting and messy text handling methods. We remove them from Node, and use a NodeText wherever we need them.

Then we extract another Satellite class from Node — perhaps one that deals with moving notes around. Pretty soon, Node can have a bunch of specialized satellites.

We haven’t broken the dependency on Node — and we’re not going to. Worse, we’ve added a new dependency, and we have to make new working objects. But when we want to change text handling, those changes now occur in NodeText, and instead of recompiling the world we only need to recompile the classes that handle text.

Eventually, we can hollow out Node until all, or almost all, its methods are merely plumbing. Everything still depends on it, but that’s OK because it’s not going to change. All the behavior is in the satellite classes, and now Node becomes simply a Facade that adapts its internal classes, like Content, to its Satellites, like NodeText. Most clients that use Node only need one Satellite, and hardly anything needs more than two.

Node becomes so simple that it seldom if ever changes. We can even add additional satellite classes without touching Node.

Extract Satellite is related to the old C++ pImpl idiom, but here we’re using the existing class to serve as the pImpl for a family of satellites, where traditionally pImpl starts with a class and extracts the implementation. Another way of looking at Extract Satellite is that it’s a Strategy turned inside out: where in Strategy the object has the data and the strategy implements the computation, here the Satellite object carries all the methods and has a pointer it can use to get the data.

OK: this is a lot more technical than is my habit, and we’ll return to our usual style next week. But it happens that I’ve read a pretty good chunk of the pertinent monograph literature on this subject, and I haven’t seen this discussed much. I thought I’d get this down for discussion, and to let curious readers know what I’ve been wrestling with this week.

Update: The refactoring is in Feathers after all, where it’s called Extract Class. Confusingly, Extract Class is used of two different approaches, and this one is tucked in as a coda after a refactoring to Decorator.