The Open-Closed Principle: Extending Your Entities Correctly

Tim Beals 🎸
Swift2Go
Published in
6 min readSep 15, 2018

Of all of the design principles enveloped in the SOLID acronym, The Open-Closed Principle, is probably the least understood and most often broken. After all, if you want to add a feature to your entity, why not just chuck it right into the entity’s existing implementation… Right? That’s certainly how I felt as a novice programmer. The purpose of this article is to first discuss the rationale behind The Open-Closed Principle and then look at three techniques for extending your entities correctly.

What is The Open-Closed Principle, and why is it important?

Let’s start with the definition as it was first written by Bertrand Meyer:

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

Essentially, we are talking about adding any new properties or methods to an entity in some sort of extension as opposed to putting them directly in the entity (and thus modifying it). That makes sense, but why on earth would we go to the trouble? Here are some reasons…

Curb the introduction of bugs

Let’s imagine that I am writing a software package that is being used by a third party and decide that I want to add some new functionality to my existing entity. On the surface this seems fine. However, when I make those changes I am not taking into consideration all of the different ways that the other entities in my system are interacting with the one I have just modified. Could my modification have unintended effects causing bugs in other parts of my system? Usually we want to develop our existing code gradually, to make it as efficient and bug free as possible. Introducing new properties or methods into this process could undo months or even years of iterative improvement.

Separate concerns

It is very possible that when I introduce new functionality to my existing entity, I will be breaking more than one of the SOLID principles. If the new functionality takes my entity beyond the scope of its original purpose, I will also be breaking The Single Responsibility Principle. In the interests of code maintainability, I should keep my original implementation encapsulated and use other means to extend it.

Design better entities

Of all the reasons to employ the Open-Closed Principle, the most important is that it encourages us to create entities with a good balance of abstraction through the use of generics and Protocol Oriented Programming (POP) principles. Just think, if I know that I can’t go back into my entity to change a type, I’m going to be very careful to design it in such a way that I am offering maximum flexibility. The upshot is that we can end up writing less code, and have the code that we do write applicable in more situations. In short, we code better.

There’s obviously a lot more discussion that could happen here, but in the interest of moving on to some useful techniques, let’s just consider one last big question…

How rigidly should I follow The Open-Closed Principle?

Adherence to The Open-Closed Principle is a subjective choice and developers often have different opinions about when to extend an entity. After all, your entity could be considered ‘completed’ or ‘a work in progress’, and ultimately you as the developer are the arbiter who makes that distinction. In my experience, if you decide that the entity requires the features you want to implement in order to meet its single responsibility, then go ahead and add them (guilt free!). Otherwise, here are some ideas for how to extend them correctly.

Extension Techniques

In this next section we will go over three extension techniques and critique them. Without further ado, let’s get acquainted with the amazing entity we will be working with in these examples:

Yup, it’s a real beaut.

Technique #1: Protocol Extension (OK)

Let’s say that I want my “Thingy” to be able to wobble (don’t ask me why…). My first instinct might be to do this:

Clearly this is in violation of our Open-Closed Principle. So, one approach could be to create a protocol declaring the method and then putting its implementation into an extension of our struct:

That works nicely. Notice that the declaration of our method takes place in the protocol (line 9) and it’s implementation occurs in the extension of our entity (line 15). One variation we might consider is putting the implementation of the method into a protocol extension. This would allow us to have the same implementation of the method across any entity that conforms to the protocol. Here is what that would look like:

The biggest difference to note here is that the declaration and implementation of our wobble method are now both in the extension of our protocol (line 9–15). When the entity conforms to the protocol (line 17), there is now no need to implement the method.

So with this first set of examples we have technically extended our entity without modifying it, but beware! First, this techniques only works for methods and not properties as we are unable to store anything in extensions. And second, perhaps an even greater (but less obvious) short-coming is that we can no longer use the original implementation of “Thingy” as it was before we added the wobble method.

Technique #2: Class Inheritance (OK)

Now, I want to give my “Thingy” a size property. One common approach is to use class inheritance.

In this case the derived class is called “SizedThingy” and it inherits the name property from the base class “Thingy” (line 17). The positives here are that we can still use the unmodified base class. This is a huge step up from our first technique, and any worry around separation of concerns is satisfied. This is still however, probably not the best approach as it has forced us to use a reference type in our system (class), when we may want a value type (struct). Classes are also more expensive to allocate and when there is lots of inheritance and overriding going on, the dynamic dispatch can get rather confused and messy. Back to the drawing board…

Technique #3: Decorator Pattern (BEST)

The best approach is to use The Decorator Pattern which acts as a wrapper around our existing entity, and implementing the additional properties and methods within it. We can provide the same interface as the underlying entity and call through to it. Here is an implementation with a static instantiation:

In this example, we describe our interface with a protocol “Thing” and then conform to it in our entity “Thingy” (line 9). You will notice that the protocol has an initializer (line 3) and the name variable in our entity has a default value “Thingy” (line 11) as we want to statically pass in the type when we initialize our decorator (line 52) and have it initialize itself, becoming the underlying entity in our decorator (line 34). In order to access the name property within our underlying entity while providing a uniform interface for our client, we can use a computed property (line 25–32). To add our new property “size” to our decorator, we create a protocol “Sized” (line 15) and then have our decorator conform to it (line 21).

Compared to our previous class inheritance example, this is a significant improvement as we can keep our value type. If however we require the initialization to be more dynamic, we need a slight modification…

Variation: Dynamic Decorator Pattern

Here is the same example again, but this time with a dynamic initializer:

The main points of difference are that the protocol “Thing” no longer has an initializer required and therefore our entity “Thingy” no longer requires a default value (line 9). Now, we can create our decorator with a dependency injection. You will notice that the initializer of our decorator (line 34) now has two input parameters, the first being our underlying entity. Notice also that the input type is “Thing” (protocol) not “Thingy” (struct). This means that we have a nice layer of abstraction.

Wrap Up

In this article, we discussed The Open-Closed Principle, what it is and why it should be observed, and explored three techniques for extending our entities without directly modifying them. Hopefully, the critique of these techniques in this article will help with your own decision making when faced with extending an existing entity. The Decorator Pattern has become a mainstay in my coding and with static and dynamic initialization options, I can usually find a solution that meets my needs.

The other big point that I hope will stay with you is that the greatest benefit of following The Open-Closed Principle is that it encourages us to create entities with the right balance of abstraction for greater utility and ultimately write less code. This idea is explored in my new article Mastering Generics with Protocols: The Specification Pattern. Thank you for reading!

--

--