DEC
21
2007

Programming Styles - Why Encapsulation is a Good Thing

I was reading a blog post on beautiful code about different styles of programming earlier this week. The author was comparing the 'ruby style' of direct access to member variables with the getter/setter pattern common in Java code. His basic question was is this simply a matter of your programming background?

Here's the Java version of his example:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    
    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
    }
	
    public void render()
        …
    }
}

As you can see the Java version doesn't permit direct access to the members and instead uses accessor functions. For me, this is a good solution because I expect my code to evolve over time. Lets look at a couple of possible future versions of the same code:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    private int expensive = 0x1234;

    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
        this.expensive = doCalculation(formatter);
    }
	
    public void render()
       // Use precalculatede expensive value in fast path
        …
    }
}

In this future, we've discovered through profiling that one of the calculations we need in our render function is a performance bottle neck. To avoid the issue, we precalculate the value and simply use the cached version. Having accessor functions makes this trivial - we don't need to track down every use of the member variables and fix them as we have acheived proper encapsulation.

Of course, the future could be different. How about if we discover that the particular instances of the above object are often obscured so we don't need to render them. Well, now we might code something like this instead:

public class GlazeObject implements Renderable {
    private ClassStore store = new DefaultClassStore();
    private Formatter formatter = new DefaultFormatter();
    private bool dirty = true;
    private int expensive = 0x0000;

    public void setStore(ClassStore store) {
        this.store = store;
    }
     
    public void setFormatter(Formatter formatter) {
        this.formatter = formatter;
        this.dirty = true;
    }
	
    public void render()
       // Calculate expensive on first use then reuse it
        …
    }
}

Again, we're saved by our encapsulation. It's easy to make when the cached value needs updating because all accesses are via a single method. The downside here is that the first call to render() after the formatter is updated is slower than subsequent calls, but that is generally a good trade off.

Now all of the above examples are in Java, where we have a much more modern object format than is available to us in C++. In C++ we have to consider the dreaded binary compatibility. A C++ version might look something like this:

class GlazeObject : public Renderable
{
public:
  void setStore( ClassStore *store );
  void setFormatter( Formatter *formatter );
  void render();

private:
  class GlazeObjectPrivate *d;
};

class GlazeObjectPrivate
{
public:
  ClassStore *store;
  Formatter *formatter;
};

Here we're using the PIMPL pattern (a d pointer) to conceal the internal structure of the object from callers. This means we can change the implementation without needing to recompile things that are calling our object. Again as you see encapsulation gives us a big win. Programming is always a matter of trade offs, but frequently the good choice for the long term is to write a little bit more code and separate concerns.

Comments

With Python or Ruby you can just keep the same syntax and add properties, you don't have to over-think it because maybe some day you might change the property setter/getter, you can begin with:

    class C(object):
       def __init__(self, v):
          self.v = v

    o = C(1)
    o.v = 2
    print o.v

And then add a setter or getter:

    class C(object):
       def __init__(self, v):
          self._v = v

       def get_v(self):
           return self._v * 2.0

       def set_v(self, v):
           self._v = v / 2.0

       v = property(get_v, set_v)   

    o = C(1)
    o.v = 2
    print o.v

By gsbarbieri at Fri, 12/21/2007 - 15:34

I'm not so sure that the best aspect of getters/setters is the fact that you can more easily change the implementation in the future. In my experience if you haven't actually designed your interfaces with extension and flexibility in mind they probably are neither extensible nor flexible (or only in minor ways).

For me the most important thing about encapsulation is the protection it offers. The knowledge that nobody will have access to the internals of a class thereby maybe changing its function in unexpected ways.

I've seen to many "clever hacks" where somebody thought "mmmm, this doesn't do exactly what I want, but hey if I poke the code a bit here and twist those bits there it works!" and then they are all surprised when things come crashing down a half a year later when some new version of the code gets installed (or worse the person is not even working there anymore so you have to find what is going wrong and you're wondering why some properties have impossible values).


By quintesse at Fri, 12/21/2007 - 20:19

With Ruby your code is the same regardless you are accessing a variable or a method. This means something that now is a variable, you can transform it in the future in a method with calculates a value *and the code accessing it does not change at all*.

Imagine you have a method to print the volume of a bottle and you had stored the volumes in pints (Imperial units). Your code is:

volume = get_bottle_volume_from_database() # these are pints, you would be getting the "10" value from a database

puts "The volume is " + volume.to_s
=> The volume is 10

Now you want to move to international units (litres). You can do this:

def volume
  return get_bottle_volume_from_database() * 0.473176475 # Transform from pints to litres
end

puts "The volume is " + volume.to_s # See? "volume" is a method here (it was a variable in the former example) but our code has not changed!

This syntax allows for all kinds of magic.


By Pau Garcia i Quiles at Sat, 12/22/2007 - 16:20