The Open/Closed Principle

Tweet

I have to admit the first time I peeked at the academic definition of the Open/Closed Principle, its predicate was surprisingly clear to me. Leaving all of the technical jargon out of the picture, of course, its dictate was pretty much the mantra that we’ve heard so many times before: “Don’t hack the core”.

Well, admittedly there’s a pinch of ambiguity since there are at least two common approaches to keeping the “core” neatly preserved while extending its functionality. The first one (and why I used deliberately the term “extending”) would be appealing to Inheritance. Inheritance is perhaps the most overrated beast for reusing implementation; it’s ridiculously easy to implement but runs the risk of modeling brittle hierarchies. The second approach is Composition. It isn’t quite as straightforward as Inheritance, but it’s still a neat way to extend a program module while keeping the module in question untouched.

The issue isn’t which option is the best for extending functionality per se. The Inheritance vs. Composition debate is so old and boring that bringing it to the table would be a waste of time. The actual problem is choosing a programming methodology that permits a decent level of closure for the target module but at the same time providing it with a “hook” opening it up for extension. In theory, both Inheritance and Composition are good candidates for accomplishing this as long as they rely on the intrinsic benefits provided by an abstraction rather than a concrete, rigid implementation.

It could also be said that concrete implementations are the principle’s worst plague. Not only do they push hard against the Open/Closed Principle’s commandments, but they make it impossible to create polymorphic modules that can be consumed by client code without ugly refactoring or smelly conditionals. In the last instance, the principle’s primary area of concern is to favor the creation of highly-decoupled components whose dependencies and the modules themselves are conceptually modeled in the form of abstract contracts or interfaces, thus promoting the ubiquitous use of Polymorphism, even if the components live and breath in different application layers.

As usual, a good way to grasp what’s actually behind the curtain of the principle and how to take advantage of its benefits is by example. In this article I’ll be setting up a few approachable ones in an attempt to unveil the principle’s driving forces.

Implementing a Non-Polymorphic HTML Renderer

While the idea stems mostly from the pragmatism of experience, sometimes it’s easier to understand a concept by first showing the wrong way of doing something. In this case, I’m going to cling to that pragmatic approach and demonstrate why the implementation of system modules that aren’t designed around the idea of “Modification Closure” stated by the Open/Closed Principle may cause an explosion of fragility/rigidity artifacts.

Say we need to build a quick and dirty HTML renderer module capable of dumping a few HTML objects modeling divs, paragraphs, and the like. Following an irreverently sloppy and non-polymorphic approach, the corresponding HTML classes and the consumer module could be written as follows:

<?php
namespace LibraryView;

class HtmlDiv
{
    private $text;
    private $id;
    private $class;

    public function __construct($text, $id = null, $class = null) {
        $this->setText($text);
        if ($id !== null) {
            $this->setId($id);
        }
        if ($class !== null) {
            $this->setClass($class);
        }
    }

    public function setText($text) {
        if (!is_string($text) || empty($text)) {
            throw new InvalidArgumentException(
                "The text of the element is invalid.");
        }
        $this->text = $text;
        return $this;
    }

    public function setId($id) {
        if (!preg_match('/^[a-z0-9_-]+$/', $id)) {
            throw new InvalidArgumentException(
                 "The attribute value is invalid.");
        }
        $this->id = $id;
        return $this;     
    }

    public function setClass($class) {
        if (!preg_match('/^[a-z0-9_-]+$/', $id)) {
            throw new InvalidArgumentException(
                 "The attribute value is invalid.");
        }
        $this->class = $class;
        return $this;     
    }

    public function renderDiv() {
        return '<div' .
            ($this->id ? ' id="' . $this->id . '"' : '') .
            ($this->class ? ' class="' . $this->class . '"' : '') .
            '>' . $this->text . '</div>';
    }
}
<?php
namespace LibraryView;

class HtmlParagraph
{
    private $text;
    private $id;
    private $class;

    public function __construct($text, $id = null, $class = null) {
        $this->setText($text);
        if ($id !== null) {
            $this->setId($id);
        }
        if ($class !== null) {
            $this->setClass($class);
        }
    }

    public function setText($text) {
        if (!is_string($text) || empty($text)) {
            throw new InvalidArgumentException(
                "The text of the element is invalid.");
        }
        $this->text = $text;
        return $this;
    }

    public function setId($id) {
        if (!preg_match('/^[a-z0-9_-]+$/', $id)) {
            throw new InvalidArgumentException(
                 "The attribute value is invalid.");
        }
        $this->id = $id;
        return $this;     
    }

    public function setClass($class) {
        if (!preg_match('/^[a-z0-9_-]+$/', $id)) {
            throw new InvalidArgumentException(
                 "The attribute value is invalid.");
        }
        $this->class = $class;
        return $this;     
    }

    public function renderParagraph() {
        return '<p' .
            ($this->id ? ' id="' . $this->id . '"' : '') .
            ($this->class ? ' class="' . $this->class . '"' : '') .
            '>' . $this->text . '</p>';
    }
}
<?php
namespace LibraryView;

class HtmlRenderer
{
    private $elements = array();

    public function __construct(array $elements = array()) {
        if (!empty($elements)) {
            $this->addElements($elements);
        }
    }

    public function addElement($element) {
        $this->elements[] = $element;
        return $this;
    }

    public function addElements(array $elements) {
        foreach ($elements as $element) {
            $this->addElement($element);
        }
        return $this;
    }

    public function render() {
        $html = "";
        foreach ($this->elements as $element) {
            if ($element instanceof HtmlDiv) {
                $html .= $element->renderDiv();
            }
            else if ($element instanceof HtmlParagraph) {
                $html .= $element->renderParagraph();
            }
        }
        return $html;
    }
}

The responsibilities of HtmlDiv and HtmlParagraph are limited to rendering the corresponding HTML elements based on a few common input arguments, such as the inner text, and the “id” and “class” attributes.

The class that begs being put under a spotlight is the contrived HtmlRenderer, which certainly can be extended further down the road either via Inheritance or Composition (read open for extension). Exposing such virtue to the outside world, what could be wrong with the renderer then? Well, its most clumsy facet rests on the fact that its level of closure for modification is in this case just a bluff because the HTML objects it handles are non-polymorphic. In its current state the class is just capable of rendering batches of HtmlDiv and HtmlParagraph objects and …sorry, nothing else. If we ever want to add a new object type to its repertoire, the render() method needs to be refactored and polluted with more conditionals. To express this in the Open/Closed Principle’s terms, the class is by no means closed for modification.

Appealing to the Benefits of Polymorphism

Implementing an effective solution is be a two-step process: first and foremost, the classes responsible for engendering the HTML objects should be turned into polymorphic structures that adhere to a shared contract. Then the HtmlRenderer class should be refactored to handle any implementer of the contract without checking explicitly if it belongs to a certain type.

A segregated interface that gets the job done nicely is this:

<?php
namespace LibraryView;

interface HtmlElementInterface
{
    public function render();
}

With this contract in place, now it’s time to do some quick clean up and encapsulate all the logic shared by the HTML classes inside the boundaries of an abstract Layer Supertype.

<?php
namespace LibraryView;

abstract class AbstractHtmlElement implements HtmlElementInterface
{
    protected $text;
    protected $id;
    protected $class;

    public function __construct($text, $id = null, $class = null) {
        $this->setText($text);
        if ($id !== null) {
            $this->setId($id);
        }
        if ($class !== null) {
            $this->setClass($class);
        }
    }

    public function setText($text) {
        if (!is_string($text) || empty($text)) {
            throw new InvalidArgumentException(
                "The text of the element is invalid.");
        }
        $this->text = $text;
        return $this;
    }

    public function setId($id) {
        $this->checkAttribute($id);
        $this->id = $id;
        return $this;     
    }

    public function setClass($class) {
        $this->checkAttribute($class);
        $this->class = $class;
        return $this;     
    }

    protected function checkAttribute($value) {
        if (!preg_match('/^[a-z0-9_-]+$/', $value)) {
            throw new InvalidArgumentException(
                "The attribute value is invalid.");
        }
    }
}

Things are now looking more appealing as we’ve managed to put implementation that’s common to all of the HTML objects beneath the shell of a supertype. Though simplistic, this change automatically turns the revamped versions of the HtmlDiv and HtmlParagraph classes into slim implementers of the same interface:

<?php
namespace LibraryView;

class HtmlDiv extends AbstractHtmlElement
{    
    public function render() {
        return '<div' .
            ($this->id ? ' id="' . $this->id . '"' : '') .
            ($this->class ? ' class="' . $this->class . '"' : '') .
            '>' . $this->text . '</div>';
    }
}
<?php
namespace LibraryView;

class HtmlParagraph extends AbstractHtmlElement
{    
    public function render() {
        return '<p' .
            ($this->id ? ' id="' . $this->id . '"' : '') .
            ($this->class ? ' class="' . $this->class . '"' : '') .
            '>' . $this->text . '</p>';
    }
}

Considering that HtmlDiv and HtmlParagraph are now beautifully polymorphic structures that honor a common contract, it’s simple to refactor the pertaining HTML renderer into a consumer of any implementer of the HtmlElementInterface interface:

<?php
namespace LibraryView;

class HtmlRenderer
{
    private $elements = array();

    public function __construct(array $elements = array()) {
        if (!empty($elements)) {
            $this->addElements($elements);
        }
    }

    public function addElement(HtmlElementInterface $element) {
        $this->elements[] = $element;
        return $this;
    }

    public function addElements(array $elements) {
        foreach ($elements as $element) {
            $this->addElement($element);
        }
        return $this;
    }

    public function render() {
        $html = "";
        foreach ($this->elements as $element) {
            $html .= $element->render();
        }
        return $html;
    }
}

I’m aware that the new implementation of the HtmlRenderer class is a far cry from being mind blowing, but still it’s now a solid module that adheres to the Open/Closed Principle’s predicates. Furthermore, not only does it expose a nice level of closure for modification, as it’s feasible to feed it at runtime with multiple implementers of the HtmlElementInterface interface without amending a single chunk of it, but this feature on its own proves that it’s entirely open for extension as well. Feel free to pat yourself on the back because we’ve won the battle on two fronts!

Here’s how the module could be put to work for rendering a couple of HTML objects:

<?php
use LibraryLoaderAutoloader,
    LibraryViewHtmlDiv,
    LibraryViewHtmlParagraph,
    LibraryViewHtmlRenderer;

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader();
$autoloader->register();

$div = new HtmlDiv("This is the text of the div.", "dID", "dClass");

$p = new HtmlParagraph("This is the text of the paragraph.", "pID", "pClass");

$renderer = new HtmlRenderer(array($div, $p));
echo $renderer->render();

Closing Remarks

In this article I’ve shown you an example of how to create functional software components that faithfully adhere to the Open/Closed Principle’s commandments. Being one of the most relevant (if not the most) SOLID principles, the principle is certainly a fundamental OOP pillar whose benefits are so remarkable that it’s a shame it has remained unfairly underrated. But needless to say, the key to accomplishing such a goal requires designing the components as polymorphic elements whose behavior is explicitly defined through abstract contracts. From that point onward, what approach is the most effective to extend them (yep, the Inheritance/Composition duet shows up again), and what level of closure they must expose to the outside world, is entirely up to you.

Image via Fotolia

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.fractalsoftware.com Manuel Herrera

    Again a new beautiful article by Alejandro. Anyone trying to be a PHP programmer must read it. Please, don’t forget my claim to write it down in a book.

    • Alex Gervasio

      Hey Manuel,
      Thanks for the compliments. And don’t you worry, I still keep in mind the proposal for writing a book :) . No much time for now.

  • http://spiechu.pl Spiechu

    Very nice article, shows how to use interfaces.
    If I may recommend something it would be getting rid of the ugly array() in favor of cleaner PHP 5.4 [] array construction.

    • Alex Gervasio

      Hi Spiechu,
      Glad you found the article to be informative. Regarding the use of the “older” array syntax, in fact I wrote down the tutorial before having a fresh 5.4 installation up and running on my system. Thanks for the positive feedback :)

  • Tjorriemorrie

    I really appreciate SOLID posts with examples that I can follow. My coding only improves if I can debate my viewpoint with SOLID proof to my colleagues; and for that I need to practically understand it.

    • Alex Gervasio

      Hey Tjorriemorrie,
      Nice to hear that. I hope the post helps you to improve a little bit further the background you currently have on the SOLID principles. Keep up the good work :)

  • Boabramah Ernest

    Not that I am been bias here, but your tutorials has really help me a lot than most of the tutorials here and sometimes i wish I could show you some of the things i have been able to do with your code sample. Thanks very much Gervasio

    • Alex Gervasio

      Thanks for the positive comments Ernest. I’m really glad to know my tutorials have been of help to you :)

  • Uncle Fred

    I usually print all the “worth reading” articles I find and use them to shorten my train travels. Was reading this one today and what a pleasant train journey I had because of it.
    This was the simplest and cleanest Open/Closed principle demonstration I have seen and you’re doing a great SOLID coverage too.
    Some might say “you print something from the Internet on paper?” Well, yes. I have re-discovered many of the benefits paper pages provide: large crisp and bright A4 size reading surface. Does not spam or display unwanted ads. I’m yet to have any battery problems or application crashes. Of course, unless the Matrix goes down all crashing. I’m totally converted.
    I see this article being tagged as a Expert PHP Level and it looks like some people even need convincing. That’s just little strange coming from the C++ and Java background. In non-interpreted languages this would be simple no-brainer since amending something that’s third party compiled and provided would not be possible.

    • Alex Gervasio

      Hey Fred,
      It’s always good to read your insightful opinions, trust me. And it’s nice to know this tutorial in particular and the few others SOLID-related I wrote so far have been in general instructive. To be honest, I’m pretty much like you, as I enjoy a lot more reading “printed” things than dealing with screens. That’s the reason why I keep the classic “programming bibles” (you know, Fowler, Martin, Beck and so forth) at hand all the time in my bookshelf :).

  • Anshul

    Good one

  • http://www.web-brainz.co.uk Martyn

    Hi Alex,
    Thanks for an informative post.

    I’m interested to know why you chose to use both the abstract class “AbstractHtmlElement” and interface “HtmlElementInterface”? I would have been tempted to have render() as an abstract method in AbstractHtmlElement, change the type of the parameter in HtmlRenderer::addElement to AbstractHtmlElement, and remove the interface.

    • Alex Gervasio

      Hey Martyn,
      Thanks for the comments. Well, I decided to pick up that option simply because I like to define the contract of classes via interfaces, while keeping common functionality encapsulated in an abstract class. Even though, the approach you suggest is perfectly valid as well. If you feel more comfortable defining part of the interface in the abstract class, just go for it :)