Liskov Substitution Principle in Object Oriented Design (SOLID)

Author
By Darío Rivera
Posted On in SOLID

In a previous post, we saw an introduction to SOLID Principles in a purely theoretical way. Today we will see everything about the third SOLID principle on the list, The Liskov Substitution Principle. Let's start by giving a definition of this principle.

Definition

This principle states that objects of a program can be replaced by their subtypes without altering the correct functioning of the program. That is, an argument of a function or method that accepts a class or interface should work the same way with any subtype of the same class or interface. To make this possible, it is necessary that the signatures of the implemented or overridden methods are identical and do not add or change the TYPE of the function result (including exceptions).

Example

To illustrate this principle, let's see a practical example of a correct implementation and some common mistakes that can break this principle. Let's see then the main type of this example as the client code where it uses a Parser class.

class Client
{
    public static function printContent(Parser $parser)
    {
        echo $parser->output();
    }
}

To comply with the Liskov substitution principle, the printContent method should be able to accept any subtype of Parser and the code would still work the same way. Let's see then that Parser is a class with its own behavior in this case.

class Parser
{
    protected array $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function output()
    {
        $result = '';

        foreach($this->data as $row)
        {
            $result .= $this->line($row);
        }

        return new Result($result);
    }

    public function line(array $row)
    {
        return implode(' ', $row) . PHP_EOL;
    }
}

This class parses a multidimensional array that simulates a data table like the following:

[
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]

Let's support our example with this Result class that is only responsible for receiving a string and returning it.

class Result
{
    private string $content;    

    public function __construct(string $content)
    {
        $this->content = $content;
    }

    public function getContent(): string
    {
        return $this->content;
    }
}

Each subtype of the Parser class will be a way to generate the content string, for example, CSV or a markdown table.

class CSV extends Parser
{
    public function line(array $row)
    {
        return implode(';', $row) . PHP_EOL;
    }
}
class MarkdownTable extends Result
{
    public function line(array $row)
    {
        return '|' . implode('|', $row) . '|' . PHP_EOL;
    }
}

In this way, the "client" code could use the Parser type in the following way.

$parser = new Parser([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

// subtype CSV
$csv = new CSV([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

// subtype MarkdownTable
$markdown = new MarkdownTable([
    [1, 'Steave', 'Developer'],
    [2, 'Andreas', 'Tester'],
]);

Client::printContent($parser);
Client::printContent($csv);
Client::printContent($markdown);

Common errors

A common error usually occurs when adding preconditions to the implementations. For example, we can verify if there are at least two rows to determine the header of the markdown table.

class MarkdownTable extends Parser
{
    public function output()
    {
        if (count($this->data) < 2) {
            throw new \Exception('Missing table header');
        }

        return parent::output();
    }

    public function line(array $row)
    {
        return '|' . implode('|', $row) . '|' . PHP_EOL;
    }
}

Delivering another data type different in a subtype also breaks the Liskov substitution principle.

class MarkdownTable extends Parser
{
    public function output()
    {
        return MarkdownWrapper(parent::output());
    }
}

Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.