PHP
Article
By Alejandro Gervasio

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

By Alejandro Gervasio

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?

--ADVERTISEMENT--

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

More:
Recommended
Sponsors
The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account