Friday, February 22, 2013

Different Forces Are No Excuse for Bad Practices

Encapsulating construction is not optional.  It is a mandatory part of good design.  The reason simple and easily conveyed.  If instantiation is encapsulated, resistance to changing what concrete type is used is low.  If sprinkled throughout a code base, that resistance is high.

One of the most important kinds of changes in this family is promoting a concrete class to an abstraction, moving the behavior to a new concrete class, and changing construction logic to use the new concrete class.  Being able to perform that operation allows you to defer the creation of an abstraction until there is actual variation to encapsulate.

This concept was popularized in modern languages like Java and C#.  A typical implementation is as follows:

public class IsConcreteForNow {
  // nobody can create this directly
  private IsConcreteForNow() { }

  // force everyone to get it indirectly
  public static IsConcreteForNow GetInstance() {
    return new IsConcreteFornow();
  }
}

C++ programmers are often resistant to encapsulation of construction when presented with a "literal" translation as follows:

class IsConcreteForNow {
private:
  IsConcreteFornow() { }

public:
  IsConcreteForNow* getInstance() { return new IsConcreteForNow(); }
}

If you are a C++ programmer, it's probably already apparent why this is so objectionable.  To start doing such a thing demands that you put every object on the heap and give up stack instantiation forever.  That price seems too high to warrant the value.  It isn't too high but it seems that way to people who are accustomed to leveraging the stack for most of their short-lived objects.

When some French guy says he has a hangover, you don't translate that to "my hair hurts" or "I have wood mouth."  You translate the intended message, not the individual words.  Likewise, encapsulating construction should be translated so as to meet the need in the context of C++ without regard to how that need is met in other languages.

While C++ makes certain aspects of keeping objects on the heap a little more complex, it has a lot of cool tools that can help you in other ways.  For instance, it has the concept of a typedef, which allow you to (among other things) define how types relate to other types.  It also has a more powerful operator overloading mechanism than a lot of other languages.

These two things put together allow you to implement encapsulation of construction in a different, but still useful way as follows:

template<class T>;
class OnStack {
private:
  T core;
public:
  T* operator -> () { return &core; }
};

class IsConcreteForNow {
private:
  IsConcreteForNow() { }

public:
  typedef OnStack<IsConcreteForNow> TransientPointer;
  typedef IsConcreteForNow* PersistentPointer;

  static void CreateTransient(TransientPointer& toCreate) {
    // constructor already did the work
  }

  static void CreatePersistent(PersistentPointer& toCreate) {
    toCreate = new IsConcreteForNow();
  }

  static void DestroyPersistent(PersistentPointer& toDestroy) {
    delete toDestroy;    toDestroy = 0;
  }
};

Note how this implementation forces consumers of a class to go through its static instantiation methods while respecting the forces imposed by C++, namely the distinction between an object that should only live for a stack frame and one that should live longer than that.

This code can be consumed as follows:

IsConcreteForNow::TransientPointer transient;
IsConcreteForNow::CreateTransient(transient);

transient->DoSomething();

IsConcreteForNow::PersistentPointer persistent;
IsConcreteForNow::CreatePersistent(persistent);

persistent->DoSomething();

IsConcreteForNow::DestroyPersistent(persistent);

Later, should I decide that I need variation, I have lots of options.  If it is absolutely imperative that I create an object on the stack, I can go through all kinds of contortions to support that.  Otherwise,  I can simply switch my TransientPointer type over to being an auto pointer or something of that nature and instantiate the right type as needed.  I can make all of those changes without any effect whatsoever on my client code.

Encapsulating construction is something everyone should do with every class they write.  I've just shown you can do it with C++.  If you can do it in C++, you can do it in any language worth your attention.  If you are using a language that makes encapsulating construction seem hard then you either need to re-evaluate how you look at the forces in that language or you need to re-evaluate if that's the right language to use at all.