The Layer Supertype Pattern: Encapsulating Common Implementation in Multi-Tiered Systems

Tweet

Being one of the cornerstones of OOP, Inheritance is pretty much like a doubled-sided door which dangerously swings in both ways. When it opens to one side, it exposes a powerful mechanism that allows us to reuse implementations in a snap without having to resort to Composition. Whenever it opens to the other side, though, we see its pleasant nature quickly vanish into thin air, replaced with that of an evil and twisted beast capable of creating all sorts of rotted hierarchies, where subtypes behave so wildly different from their base types that saying there’s a “IS-A” relationship between each of them is blasphemous!

Despite all of the pitfalls and oddities rooted to Inheritance, most of which can be mitigated through rational and moderate use, its charming influence is hard to resist. Code reuse is, after all, the reason why Inheritance lives and breaths in the first place, and it can be a real killer when it comes to adding boilerplate implementations inside the abstractions of a multi-tiered system.

Inheritance offers a straightforward way to easily spawn a large number of objects that are semantically related to each other without having duplicate code. The concept is ridiculously simple – yet powerful: you first drop as much logic as possible within the boundaries of a base type (usually an abstract class, but it could be a concrete one), and then start deriving refined subtypes according to more specific requirements. The process is typically conducted in a “per-layer” basis, thus providing each layer with its own set of super types whose core functionality is distilled and extended in turn by the corresponding subtypes.

Not surprisingly, this repetitive encapsulation/derivation cycle lays down on the formalities of a design pattern known as Layer Supertype, (yes, although somewhat naïve, it does have a real academic name), and in the upcoming lines I’ll be taking an in-depth look at its inner workings and you’ll be able to see just how easy it is to hook up its functionality to a Domain Model.

The Need for a Layer Supertype – Defining a Bloated Domain Model

It could be said that a Layer Supertype is the natural and selective evolution of “common” base types, only that the latter live and breathe inside the confines of a specific layer. This has a prolific niche in multi-tiered designs, where exploiting the functionality of a supertype is mostly an imperative need, not just a flippant decision.

As usual, the most effective way to understand the pragmatism behind the pattern is through a few hands-on examples. So, say we need to build a simple Domain Model from scratch, responsible for defining a few basic interactions between some blog posts and their corresponding comments.

In a cursory fashion, the model could be easily outlined as an anemic layer containing just a couple of skeletal classes modeling the posts and comments.

The first domain class, along with its contract, could look like this:

<?php
namespace Model;

interface PostInterface
{
    public function setId($id);
    public function getId();
    
    public function setTitle($title);
    public function getTitle();
    
    public function setContent($content);
    public function getContent();
    
    public function setComment(CommentInterface $comment);
    public function setComments(array $comments);
    public function getComments();
}
<?php
namespace Model;

class Post implements PostInterface
{
    protected $id;
    protected $title;
    protected $content;
    protected $comments = array();

    public function __construct($title, $content, array $comments = array()) {
        $this->setTitle($title);
        $this->setContent($content);
        if (!empty($comments)) {
           $this->setComments($comments); 
        }
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this post has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The post ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setTitle($title) {
        if (!is_string($title) 
            || strlen($title) < 2 
            || strlen($title) > 100) {
            throw new InvalidArgumentException(
                "The post title is invalid.");
        }
        $this->title = htmlspecialchars(trim($title),
            ENT_QUOTES);
        return $this;
    }
    
    public function getTitle() {
        return $this->title;
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException(
                "The post content is invalid.");
        }
        $this->content = htmlspecialchars(trim($content),
            ENT_QUOTES);
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setComment(CommentInterface $comment) {
        $this->comments[] = $comment;
        return $this;
    }
    
    public function setComments(array $comments) {
        foreach ($comments as $comment) {
            $this->setComment($comment);
        }
        return $this;
    }
    
    public function getComments() {
        return $this->comments;
    }
}

Driving the Post class is banal logic that boils down to defining the data and behavior of a few basic post entries. It should be pretty simple to understand.

Now let’s make the model a little fatter by adding to it a class that spawns the comments associated with a particular blog entry. It’s contract and implementation look like this:

<?php
namespace Model;

interface CommentInterface
{
    public function setId($id);
    public function getId();
    
    public function setContent($content);
    public function getContent();
     
    public function setAuthor($author);
    public function getAuthor();
}
<?php
namespace Model;

class Comment implements CommentInterface
{
    protected $id;
    protected $content;
    protected $author;

    public function __construct($content, $author) {
        $this->setContent($content);
        $this->setAuthor($author);
    }
    
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this comment has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
                "The comment ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 2) {
            throw new InvalidArgumentException(
                "The content of the comment is invalid.");
        }
        $this->content = htmlspecialchars(trim($content),
            ENT_QUOTES);
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setAuthor($author) {
        if (!is_string($author) || strlen($author) < 2) {
            throw new InvalidArgumentException(
                "The author is invalid.");
        }
        $this->author = $author;
        return $this;
    }
    
    public function getAuthor() {
        return $this->author;
    }
}

Like Post, the Comment class is pretty naïve as well. But now with both classes in place, we can put the model to use. For example:

<?php
use LibraryLoaderAutoloader,   
    ModelPost,
    ModelComment;
    
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$post = new Post(
    "A sample post.",
    "This is the content of the post."
);

$post->setComments(array(
    new Comment(
        "One banal comment for the previous post.",
        "A fictional commenter"),
    new Comment(
        "Yet another banal comment for the previous post.",
        "A fictional commenter")
));

echo $post->getTitle() . " " . $post->getContent() . "<br>";

foreach ($post->getComments() as $comment) {
    echo $comment->getContent() . " " . $comment->getAuthor() .
        "<br>";
}

That worked like a charm indeed! Consuming the model is a fairly straightforward process, requiring that you first create a few Post objects and then hydrating them with the related comments. Yes, life is sweet and good. Well, so far it is, but things could definitely be much better!

Not that I want to spoil the magic of such a beautiful moment, but I must confess a slight shiver run down my spine each time I look at the implementations of the Post and Comment classes. While it isn’t a serious issue per se, some methods, such as setId() and setContent(), exhibit the typical symptoms of code duplication.

Solving this problem without sloppy isn’t so intuitive as it might seem at first glance because of a few logical concerns. First, although they share a semantic relationship with each other, each class effectively models different types of objects. Second, they implement disparate interfaces which means it’s somewhat hard to abstract away the logic without ending up with a clumsy hierarchy where the “IS-A” condition never holds true.

In this case in particular, we could just take a more liberal approach and think of Post and Comment as subtypes of a highly-generic AbstractEntity supertype. In doing so, it would be pretty simple to put shared implementation inside the boundaries of an abstract class, hence making the definitions of the subtypes a lot thinner. Since the whole abstraction process would be only conducted in the domain layer, the hypothetical AbstractEntity would be considered… yes, you guessed right, a Layer Supertype. Simple but nice, huh?

Moving Shared Implementation to a Single Domain Class – Creating a Layer Supertype

The process of creating a model supertype, at least in this case, can be viewed as a mixture of a few atomic and granular refactoring techniques, such as Extract SuperClass, Pull Up Field, and Pull Up Method all covered in depth by Martin Fowler in his book Refactoring: Improving the Design of Existing Code (if you don’t own a copy already, make sure to grab one ASAP).

Let’s take the big plunge and refactor our clunky domain model by dropping into it the aforementioned AbstractEntity class. Since in this specific situation the bulk of duplicated code appears through the methods of the domain classes that handle IDs and perform some basic validation on string-based fields, it’d be useful to delegate these responsibilities to the supertype and let the subtypes be focused on doing fewer, more narrowed tasks.

Based on this simplistic concept, a “fat” implementation of the aforementioned model supertype might look like this:

<?php
namespace Model;

class AbstractEntity
{
    protected $id;
    
    // Map calls to protected/private fields to mutators when
    // defined. Otherwise, map them to the fields.
    public function __set($field, $value) {   
        $this->checkField($field);
        $mutator = "set" . ucfirst(strtolower($field));
        method_exists($this, $mutator) && is_callable(array($this, $mutator)) 
            ? $this->$mutator($value)
            : $this->$field = $value;
        return $this;                 
    }

    // Map calls to protected/private fields to accessors when
    // defined. Otherwise, map them to the fields.
    public function __get($field) {
        $this->checkField($field);
        $accessor = "get" . ucfirst(strtolower($field));
        return method_exists($this, $accessor) && is_callable(array($this, $accessor))
            ? $this->$accessor() 
            : $this->$field;
    }
    
    // Map calls to undefined mutators/accessors to the corresponding
    // fields
    public function __call($method, $arguments) {
        if (strlen($method) < 3) {
            throw new BadMethodCallException(
                "The mutator or accessor '$method' is not valid for this entity.");
        }
        $field = lcfirst(substr($method, 3));
        $this->checkField($field);
        if (strpos($method, "set") === 0) {
            $this->$field = array_shift($arguments);
            return $this;
        }
        if (strpos($method, "get") === 0) {
            return $this->$field;
        }
    }

    // Make sure IDs are positive integers and assigned only once
    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this entity has been set already.");
        }
        if (!is_int($id) || $id < 1) {
            throw new InvalidArgumentException(
              "The ID for this entity is invalid.");
        }
        $this->id = $id;
        return $this;
    }
    
    public function getId() {
        return $this->id;
    }
    
    // Get the entity fields as an array
    public function toArray() {
        return get_object_vars($this);
    }
    
    // Check if the given field exists in the entity
    protected function checkField($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException(
                "Setting or getting the field '$field' is not valid for this entity.");
        }
    }
    
    // Validate and sanitize a string
    protected function sanitizeString($value, $min = 2, $max = null) {
        if (!is_string($value) || empty($value)) {
            throw new InvalidArgumentException(
              "The value of the current field must be a non-empty string.");
        }
        if (strlen($value) < (integer) $min || $max ? strlen($value) > (integer) $max : false) {
            throw new InvalidArgumentException(
              "Trying to assign an invalid string to the current field.");
        }
        return htmlspecialchars(trim($value), ENT_QUOTES);
    }
}

Contrary to popular belief, bringing to life an abstract supertype which neatly encapsulates most of the logic shared by the model’ subtypes under its hood is actually an easy process that can be accomplished relatively quickly. In this case, I admit I became a bit overzealous as the supertype not only is capable of handling IDs and validating/sanitizing strings, but is also sprinkles in some PHP magic behind the scenes to map calls to private/protected properties to their corresponding mutators/accessors whenever possible. Providing the supertype with extra functionality like this right out of the box is entirely optional, so feel free include it or omit it in your own AbstractEntity class as you see fit.

The next step is to refactor the implementations of the Post and Comment classes so they can accommodate the goodies their abstract parent now offers. Below are the refactored versions:

<?php
namespace Model;

class Post extends AbstractEntity implements PostInterface
{
    protected $title;
    protected $content;
    protected $comments = array();

    public function __construct($title, $content, array $comments = array())  {
        $this->setTitle($title);
        $this->setContent($content);
        if (!empty($comments)) {
            $this->setComments($comments);
        }
    }
    
    public function setTitle($title) {
        try {
            $this->title = $this->sanitizeString($title);
            return $this; 
        }
        catch (InvalidArgumentException $e) {
            throw new $e("Error setting the post title: " .
                $e->getMessage());
        }
    }
    
    public function getTitle() {
        return $this->title;
    }
    
    public function setContent($content) {
        try {
            $this->content = $this->sanitizeString($content);
            return $this;
        }
        catch (InvalidArgumentException $e) {
            throw new $e("Error setting the post content: " .
                $e->getMessage());
        } 
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setComment(CommentInterface $comment) {
        $this->comments[] = $comment;
        return $this;
    }
    
    public function setComments(array $comments) {
        foreach ($comments as $comment) {
            $this->setComment($comment);
        }
        return $this;
    }
    
    public function getComments() {
        return $this->comments;
    }
}
<?php
namespace Model;

class Comment extends AbstractEntity implements CommentInterface
{
    protected $content;
    protected $author;

    public function __construct($content, $author) {
        $this->setContent($content);
        $this->setAuthor($author);
    }
    
    public function setContent($content) {
        try {
            $this->content = $this->sanitizeString($content);
            return $this;
        }
        catch (InvalidArgumentException $e) {
            throw new $e("Error setting the comment: " .
                $e->getMessage());
        }
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setAuthor($author) {
        try {
            $this->author = $this->sanitizeString($author);
            return $this;
        }
        catch (InvalidArgumentException $e) {
            throw new $e("Error setting the author : " .
                $e->getMessage());
        }
    }
    
    public function getAuthor() {
        return $this->author;
    }
}

The model’s subtypes are now a lot slimmer creatures, as most of the duplicated code has been placed within the dominant supertype. What’s more, because of the implementation under the surface of the __set()/__get() duet, consuming the model is even an easier task, reduced to just coding the following snippet:

<?php
$post = new Post("A sample post.", "This is the content of the post.");

$post->setComments(array(
    new Comment(
        "One banal comment for the previous post.",
        "A fictional commenter"),
    new Comment(
        "Yet another banal comment for the previous post.",
        "A fictional commenter")
 ));

echo $post->title . " " . $post->content . "<br>";

foreach ($post->comments as $comment) {
    echo $comment->content . " " . $comment->author . "<br>";
}

The example might be contrived, but it does highlight how to get the model finally up and running, this time using the trimmed versions of the Post and Comment classes. The juicy stuff actually happens behind the scenes rather than on the flashy front line, since the definition of an abstract supertype entity allows us to remove large chunks of duplicated implementations without much worry during the whole refactoring process.

Moreover, the sole existence of a few subtypes is, on its own, a valid reason for going through the hassle of implementing a Layer Supertype. For obvious reasons, the pattern’s most appealing facet is exposed when dealing with multiple subtypes scattered through several application layers.

Closing Remarks

Although its commonly viewed as an overrated and abused beast, I hope now few will disagree that Inheritance is a powerful mechanism which, when cleverly employed in multi-tiered systems, can be an effective repellent of code duplication. The use of a simplistic pattern such as Layer Supertype is an example of the wealth of engaging virtues that Inheritance provides right out of the box when it comes to creating subtypes that share extensive segments of boilerplate implementation with each other.

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.

  • Dani from Jerusalem

    Great article. I love this blog. Please keep the posts coming!

    • Alex Gervasio

      Hey Dani,
      Glad the article has been instructive. Thanks for the kind comments:)

  • Dejan

    Great post, thanks.
    Everything OOP related here is pure gold.
    Keep up the good work

    • Alex Gervasio

      Thanks Dejan for the feedback. Really appreciated.

  • Igor

    Love your posts, Alejandro. Very clear and concise. Thanks!

    • Alex Gervasio

      Hi Igor,
      Nice to know the post has been informative. Thanks for dropping your comments.

  • alireza

    Hello Alex. Great Article.
    From Iran.

    • Alex Gervasio

      Hello alireza,
      Thanks for the kind words :)

  • Rino André Johnsen

    Hi, Alex!
    Once again a fantastic article. I really enjoy the fact that you use the post/comments example over again. Makes it easier for me to keep track of whats new for every new article. Thanks for writing these articles!

    • Alex Gervasio

      Hey Rino,
      Glad you enjoyed the article. Thanks for the positive feedback :)

  • Viktor

    Hi Alex,
    Unfortunately after reading this article I do not understand what you wrote. Please write articles understandable.

    • Alex Gervasio

      Hi Viktor,
      The article should be pretty easy to grasp overall, though not that I’m claiming the topic should be digestible for every newcomer making their first steps in the OOP terrain. If you have under your belt at least an average background on a few BASIC OOP concepts, such as Inheritance and the like, you shouldn’t have major issues understanding the concepts discussed along the article. Thanks.

  • http://nerdinacan.com Mason

    Now that PHP 5.4 is here, what do you think about the idea of making all your dynamic get/set features into a trait instead of a base class?

    • http://WebsiteURL Alex Gervasio

      Hi Mason,
      Traits are pretty nice when it comes to packaging boilerplate implementation in just one centralized place, so in that “relaxed” sense you’re free to pick them up in favor of plain abstract classes if that makes you feel a little more comfortable. Strictly speaking, though, they won’t let you achieve true multiple Inheritance at all, which means that neither a class consuming a trait is a subtype of it, nor the trait is a supertype of that class. This can become pretty much an issue when using type hinting in method signatures, specially if client code expects to get an object that honors a specific contract. Of course, in such cases you can use explicitly interface constructs for declaring the contract in question.
      To sum up: if you don’t need to be that rigorous on types, or simply don’t rely on design by contract, both traits and abstract classes are perfectly valid options for placing shared implementation. Thanks.

      • http://nerdinacan.com Mason

        That’s largely the conclusion I came to but I think you described it a lot better than I did. What I’ve been doing recently is mixing peanut butter with my chocolate. I ended up using an abstract base class, very similar to what you’ve done here, but …. with traits defining the __set, __get, __call, etc. so that I can re-use those dynamic accessors in non-model objects.

        • Alex Gervasio

          At first blush, I wouldn’t say that’s necessarily a wrong mixture. Quite possibly, you may want to use a little more moderately the magic methods throughout your application layers, cause this always ends up pushing against the definition of clean, declarative APIs. The pragmatism of PHP’s magic is unquestionably a tempting fruit, but I personally attempt to sink my teeth into it with caution :)