Choosing the correct openings and closures

Plenty of programmers will have heard of the Open-Closed Principle of object-oriented design. It is, after all, one of the five SOLID principles. You may not, however, have seen the principle as originally stated. You’ve probably heard this formulation by Robert C. Martin, or a variation on the theme:

Modules that conform to the open-closed principle have two primary attributes.

  1. They are “Open For Extension”.
    This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications.

  2. They are “Closed for Modification”.
    The source code of such a module is inviolate. No one is allowed to make source code changes to it.

Source: “The Open-Closed Principle”, the Engineering Notebook, Robert C. Martin, 1996

OK, so how can we add stuff to a module or make it behave “in different ways” if we’re not allowed to make source code changes? Martin’s solution involves abstract classes (because he’s writing for a C++ journal, read “interfaces” or “protocols” as appropriate to your circumstances). Your code makes use of the abstract idea of, say, a view. If views need to work in a different way, for some reason (say someone needs to throw away some third-party licensed display server and replace it with something written in-house) then you don’t edit the view you’ve already provided, you replace that class with your new one.

The Open-Closed Principle was originally written by Bertrand Meyer in the first edition of his book, Object-Oriented Software Construction. Here’s what he had to say:

A satisfactory modular decomposition technique must satisfy one more requirement: it should yield modules that are both open and closed.

  • A module will be said to be open if it is still available for extension. For example, it should be possible to add fields to the data structures it contains, or new elements to the set of functions it performs.
  • A module will be said to be closed if [it] is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding). In the case of a programming language module, a closed module is one that may be compiled and stored in a library, for others to use. In the case of a design or specification module, closing a module simply means having it approved by management, adding it to the project’s official repository of accepted software items (often called the project baseline), and publishing its interface for the benefit of other module designers.

Source: Object-Oriented Software Construction, Bertrand Meyer, 1988 (p.23)

Essentially the idea behind a “closed” module for Meyer is one that’s baked; it has been released, people are using it, no more changes. He doesn’t go as far as Martin later did; there are no changes to its data structure or functionality. But if a module has been closed, how can it still be open? “Aha,” we hear Meyer say, “that’s the beauty of inheritance. Inheritance lets you borrow the implementation of a parent type, so you can open a new module that has all the behaviour of the old.” There’s no abstract supertype involved, everything’s concrete, but we still get this idea of letting old clients carry on as they were while new programmers get to use the new shiny.

Both of these programmers were suggesting the “closedness” of a module as a workaround to limitations in their compilers: if you add fields to a module’s data structure, you previously needed to recompile clients of that module. Compilers no longer have that restriction: in [and I can’t believe I’m about to say this in 2013] modern languages like Objective-C and Java you can add fields with aplomb and old clients will carry on working. Similarly with methods: while there are limitations in C++ on how you can add member functions to classes without needing a recompile, other languages let you add methods without breaking old clients. Indeed in Java you can add new methods and even replace existing ones on the fly, and in Smalltalk-derived languages you can do it via the runtime library.

But without the closed part of the open-closed principle, there’s not much point to the open part. It’s no good saying “you should be able to add stuff”, of course you can. That’s what the 103 keys on your keyboard that aren’t backspace or delete are for. This is where we have to remember that the compiler isn’t the only reader of the code: you and other people are.

In this age where we don’t have to close modules to avoid recompiles, we should say that modules should be closed to cognitive overload. Don’t make behavioural changes that break a programmer’s mental model of what your module does. And certainly don’t make people try to keep two or more mental models of what the same class does (yes, NSTableView cell-based and view-based modes, I am looking at you).

There’s already a design principle supposed to address this. The Single Responsibility Principle says not to have a module doing more than one thing. Our new version of the Open-Closed Principle needs to say that a module is open to providing new capabilities for the one thing it does do, but closed to making programmers think about differences in the way that thing is done. If you need to change the implementation in such a way that clients of the module need to care about, stop pretending it’s the same module.

About Graham

I make it faster and easier for you to create high-quality code.
This entry was posted in OOP. Bookmark the permalink.