Wednesday, November 14, 2012

Encapsulation: You're doing it wrong

In the last post, I investigated just what the devil encapsulation actually is.  I may not have answered that question, but I did decide that whatever it means, there's a subtle but important distinction to be made around encapsulating "data".  The example that launched that distinction was a Queue which stores data from the caller in some encapsulated implementing data structure.  Notice the distinction between the caller's data, and the Queue's implementation.

One way of approaching a new OO design in a "business" environment is to ask, what data do I have?  Then create a "model", and add a property for each data element.  C#'s { get; set; } properties highly encourage this, and ORM and ActiveRecord tools require it.  So now we have little data classes, structures basically.  But we know that we're not doing OO unless we're doing encapsulation, and that means we need some methods!  So we add some methods to our little data classes that usually either modify that data in some way, or perform some calculations with it.

But what is this class encapsulating?  All the data is fully exposed, and the methods are restricted to simple operations on the same data.  Clearly it's trying to represent something, but we started from some data which more than likely corresponds directly to a database table.  So what is it representing?  At best, one data thing.  And what is it encapsulating?  Some logic about that data.

But looking at this again from the perspective of encapsulation as bundling implementation details instead of data, we could go a different route.  When thinking about a Queue, I don't think about it's internal implementing data structure.  I think about the operations I want it to perform for me.  So instead of asking, "what data do I have?", "what operations do I need to perform?" could be better starting point.

What if all the properties were moved off the object onto their own little class -- or structure -- or in F#, record.  The original object would then be left with operations only.  And one of those operations would have to be getting the data, and that would just be a simple method that returned the little data class/structure/record.  This class is encapsulating the implementation details of those operations you decided you needed to perform!  And just the like the Queue, there is now a clear distinction between the caller's data and the class's implementation.

A number of interesting benefits follow from this:
  • Enables a coarser grained interface, which is especially useful for data access.  You gain the control to define operations to retrieve as little or as much data as you need.
  • Designing around encapsulating implementation details leads to objects that are well defined with intuitive behaviors and clear purpose.  Ultimately that means it's easier to find the behavior you want, and extend behavior when needed.
  • The resulting clean behavioral interface, passing and returning data, immediately results in simple and flexible decoupling, which is great for unit testing.
And these are just the benefits realized at the level of just the one class we modified.  In the next post I want to look at what happens when this architecture is applied through out in what I call a stratified design.