Sunday, February 21, 2010

Is Encapsulation Creating Duplication?

The answer is a definite "no."  Each is the other's anathema.  Yet sometimes it looks like concision and encapsulation are at odds.

Let's take the simple example of encapsulating construction.  When you encapsulate a constructor, you might do something very simple as follows:

public class MyService {
  private MyService() { }


  public static MyService GetInstance() {
    return new MyService();
  }
}

The argument for encapsulating construction are pretty ironclad.  It's essentially free, and it lets you promote a class from a concrete class to an abstract one.  At least, theoretically speaking...

When you get into it, you find that something very annoying occurs.  Let's say that we need a new parameter when producing instances of MyService.  We have to make that change in two places.


public class MyService {
  private MyService(string parameter1) { }

  public static MyService GetInstance(string parameter1) {
    return new MyService(parameter1);
  }
}


This is arguably unavoidable: the system was closed to variation in whatever parameter1 represents.  We had to refactor the system to be open to that kind of variation and closed to future change pertaining to the same concept.  Yes we had to make a change in two places to be complete but we did it to improve design so its okay.

What about when we add another parameter?  Is that just another refactor?  What about when we want to delegate some of the work off to another method?  Do we now have to maintain two parameters in three signatures and some number of method calls?  In areas where change is the most common, the problem becomes clear quickly.

"Keep it simple!" someone might cry, "That's what you did wrong, you made things complex by adding a whole bunch of unnecessary methods."

Nonsense.

The problem isn't that I improved design.  The problem is that I missed an opportunity to improve design again when I added the parameter.  I made the called methods open-closed to the arguments with which they could be called (which is the whole point of a parameter) but I didn't encapsulate them from changes to the arguments, themselves.

What if I had done something like the following, instead?

public class MyService {
  public class InstantiationArguments {
    public string Parameter1 { get; set; }
  }



  private MyService(InstantiationArguments arguments) { }

  public static MyService GetInstance(InstantiationArguments arguments) {
    return new MyService(parameter1);
  }
}


That would have been a much better refactor.  For one thing, I would have been applying the open-closed principle correctly - rendering the thing I am refactoring open-closed to the kind of change I am introducing (changes to method signature).  For another, I could have done my work in smaller steps: first making the instantiation behavior open-closed to its parameters, then adding the parameter and handling its value.

The point here is this: the principles of design seem unassailable; at least, with our current knowledge.  When it look like a principle isn't working, it is most likely that you (or I) just cannot see how it applies and less likely that the principle is wrong or requires refinement.

There is no room for "moderation" when adhering to principles.  If there were, they wouldn't be principles.  You either refine them, figure out how to follow them, or perish at the hands of someone more competitive.