Skip to content

Programming Styles - Why Encapsulation is a Good Thing

Friday, 21 December 2007  |  rich

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.