The Open/Closed Principle

Share this article

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

Alejandro GervasioAlejandro Gervasio
View Author

Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.

Expert
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form