© Matthias Noback 2018
Matthias NobackPrinciples of Package Designhttps://doi.org/10.1007/978-1-4842-4119-6_5

5. The Dependency Inversion Principle

Matthias Noback1 
(1)
Zeist, The Netherlands
 
The last of the SOLID principles of class design focuses on class dependencies. It tells you what kinds of things a class should depend on1:

Depend on abstractions, not on concretions.

The name of this principle contains the word “inversion,” from which we may infer that without following this principle we would usually depend on concretions, not on abstractions. The principle tells us to invert that direction: we should always depend on abstractions.

Example of Dependency Inversion: the FizzBuzz Generator

There is a well-known programming assignment that serves as a nice example of dependency inversion. It’s called “FizzBuzz” and is often used as a little test to see if a candidate for a programming job could manage to implement a set of requirements, usually on the spot. The requirements are these:
  • Generate a list of numbers from 1 to n.

  • Numbers that are divisible by 3 should be replaced with Fizz.

  • Numbers that are divisible by 5 should be replaced with Buzz.

  • Numbers that are both divisible by 3 and by 5 should be replaced with FizzBuzz.

Applying these rules, the resulting list would become:
  • 1, 2, Fizz, 4, Buzz … 13, 14, FizzBuzz, 16, 17 …

Since not all the list’s elements are integers, the resulting list should be a list of strings. A straightforward implementation might look like the one shown in Listing 5-1.
class FizzBuzz
{
    public function generateList(int $limit): array
    {
        $list = [];
        for ($number = 1; $number <= $limit; $number++) {
            $list[] = $this->generateElement($number);
        }
        return $list;
    }
    private function generateElement(int $number): string
    {
        if ($number % 3 === 0 && $number % 5 === 0) {
            return 'FizzBuzz';
        }
        if ($number % 3 === 0) {
            return 'Fizz';
        }
        if ($number % 5 === 0) {
            return 'Buzz';
        }
        return (string)$number;
    }
}
$fizzBuzz = new FizzBuz();
$list = $fizzBuzz->generateList(100);
Listing 5-1

An Implementation of the FizzBuzz Algorithm

Given the assignment, this is a very accurate implementation of the requirements. Reading through the code, we are able to recognize every requirement in it: the rules about the divisibility of the numbers, the requirement that the list of numbers starts at 1, etc.

Once the candidate has produced some code like this, the interviewer adds another requirement:
  • It should be possible to add an extra rule without modifying the FizzBuzz class.

Making the FizzBuzz Class Open for Extension

Currently the FizzBuzz class is not open for extension, nor closed for modification. If numbers divisible by 7 should one day be replaced with Whizz, it will be impossible to implement this change without actually modifying the code of the FizzBuzz class.

Pondering about the design of the FizzBuzz class and how we can make it more flexible, we note that the generateElement() method contains lots of details. Within the same class, though, the generateList() method is rather generic. It just generates a list of incrementing numbers, starting with 1 (which is somewhat specific), and ending with a given number. So the FizzBuzz class has two responsibilities: it generates lists of numbers, and it replaces certain numbers with something else, based on the FizzBuzz rules.

These FizzBuzz rules are liable to change. And the requirement is that when the rules change, we should not need to modify the FizzBuzz class itself. So let’s apply some things that we’ve learned in the chapter about the Open/Closed principle. For starters, we can extract the rules into their own classes and use them in generateElement() , as shown in Listing 5-2.
class FizzBuzz
{
    public function generateList(int $limit): array
    {
        // ...
    }
    private function generateElement(int $number): string
    {
        $fizzBuzzRule = new FizzBuzzRule();
        if ($fizzBuzzRule->matches($number)) {
            return $fizzBuzzRule->getReplacement();
        }
        $fizzRule = new FizzRule();
        if ($fizzRule->matches($number)) {
            return $fizzRule->getReplacement();
        }
        $buzzRule = new BuzzRule();
        if ($buzzRule->matches($number)) {
            return $buzzRule->getReplacement();
        }
        return (string)$number;
    }
}
Listing 5-2

Extracting a Method for Generating Separate Elements Using “Rules”

The details about the rules can be found in the specific rule classes. Listing 5-3 shows an example of the “Fizz” rule, as implemented in the FizzRule class.
class FizzRule
{
    public function matches($number): bool
    {
        return $number % 3 === 0;
    }
    public function getReplacement(): string
    {
        return 'Fizz';
    }
}
Listing 5-3

A Class that Represents One of the FizzBuzz Rules

This is one step in the right direction. Even though the details about the rules (the numbers 3, 5, 3 and 5, and their replacement values) have been moved to the specific rule classes, the code in generateElement() remains very specific. The rules are still represented by (very specific) class names, and adding a new rule would still require a modification of the generateElement() method, so we haven’t exactly made the class open for extension yet.

Removing Specificness

We can remove this specificness from the FizzBuzz class by introducing an interface (see Listing 5-4) for the rule classes and allowing multiple rules to be injected into a FizzBuzz instance.
interface RuleInterface
{
    public function matches($number): bool;
    public function getReplacement(): string;
}
class FizzBuzz
{
    private $rules = [];
    public function addRule(RuleInterface $rule): void
    {
        $this->rules[] = $rule;
    }
    public function generateList($limit): array
    {
        // ...
    }
    private function generateElement(int $number): string
    {
        foreach ($this->rules as $rule) {
            if ($rule->matches($number)) {
                return $rule->getReplacement();
            }
        }
        return $number;
    }
}
Listing 5-4

Introducing Abstraction

Now we need to make sure that every specific rule class implements the RuleInterface and then the FizzBuzz class can be used to generate lists of numbers with varying rules, as shown in Listing 5-5 and Figure 5-1.
class FizzRule implements RuleInterface
{
    // ...
}
$fizzBuzz = new FizzBuzz();
$fizzBuzz->addRule(new FizzBuzzRule());
$fizzBuzz->addRule(new FizzRule());
$fizzBuzz->addRule(new BuzzRule());
// add more rules if you want, e.g.
// $fizzBuzz->addRule(new WhizzRule());
// ...
$list = $fizzBuzz->generateList(100);
Listing 5-5

Setting Up a FizzBuzz Instance with Concrete Rules

../images/471891_1_En_5_Chapter/471891_1_En_5_Fig1_HTML.jpg
Figure 5-1

FizzBuzz with concrete dependencies

Now we have a highly generic piece of code, the FizzBuzz class, which “generates a list of numbers, replacing certain numbers with strings, based on a flexible set of rules”. There’s no mention of “FizzBuzz” in that description and there’s no mention of “Fizz” nor “Buzz” in the code of the FizzBuzz class . Actually, the FizzBuzz class may be renamed so that it better communicates its responsibility. Of course, naming things is one of the hardest parts of our job and NumberListGenerator isn’t a particularly expressive name, but it would better describe its purpose than its current name.

Looking at the initial implementation of the FizzBuzz class, it has become clear that the class had an abstract task from the start: to generate a list of numbers. Only the rules were highly detailed (being divisible by 3, by 5, etc.). To use the words from the Dependency Inversion principle: an abstraction depended on concrete things. This caused the FizzBuzz class to be closed for extension, as it was impossible to add another rule without modifying it.

By introducing the RuleInterface and adding specific rule classes that implemented this interface, we fixed the dependency direction. The FizzBuzz class started to depend on more abstract things, called “rules” (see Figure 5-2). When creating a new FizzBuzz instance, concrete implementations of RuleInterface have to be injected in the right order. This will result in the correct execution of the FizzBuzz algorithm. The FizzBuzz class itself is no longer concerned about it, which is why the class ends up being more flexible with regard to changing requirements. This is exactly the way things should be according to the Dependency Inversion principle2:

Abstractions should not depend upon details. Details should depend upon abstractions.

../images/471891_1_En_5_Chapter/471891_1_En_5_Fig2_HTML.jpg
Figure 5-2

FizzBuzz with abstract dependencies

Now that we’ve seen the Dependency Inversion principle in action, we can take a look at some situations where it’s clearly being violated.

Violation: A High-Level Class Depends on a Low-Level Class

The first violation arises from mixing different levels of abstraction . It’s an interesting concept that needs further explanation before we dive into the example.

On a daily basis we have to deal with a great diversity of things that exist in the universe. We wouldn’t be able to do anything meaningful if we’d consider every little detail of everything we talk about, everything we use while doing our job, everyone we love. So to preserve our sanity, we come up with abstractions all the time. “Abstraction” means “taking away the details”. What remains is a concept that can we can use to group all the specific things from which the abstraction has been created, and a name for what’s essential to all these specific things, ignoring the little differences.

In conversation we usually end up establishing some level of abstraction, so we can safely ignore the details (or the larger picture). When discussing software design, this means we zoom in or out until we can address the issue at hand. For instance, when discussing a code smell, we’ll be talking about method signatures, variable names, etc. so we can safely ignore the message queue software, or the particular Linux filesystem that we use on our server. When we talk about how the application gets data from the database, we discuss SQL queries, so we can ignore the underlying TCP protocol that’s being used.

Zooming in and out is the same as moving from abstraction to concretion and back again. The more we zoom in on a part of our software, the closer we get to the low-level details (also known as “internals”). The more we zoom out, the closer we get to a high-level view of the system; what features it aims to provide to its users.

In class design, we have to consider the same kind of zooming in and out. Every class has two levels of abstraction: the first is the one perceived by clients. The second is the one that’s going on inside. By definition, a class or an interface is going to hide some implementation details for its client, meaning that the clients will perceive it to be more abstract, while the class internally is more concrete.

So a class’s internals are always more concrete than the abstraction that the class represents. However, when a class depends on some other class, it should again depend on something that is abstract, not concrete. That way, the class itself becomes a client of something abstract and can safely ignore all the underlying details of how that dependency works under the hood.

As an example of a class that has a dependency that isn't abstract, consider the Authentication class in Listing 5-6.
class Authentication
{
    private $connection;
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }
    public function checkCredentials(
        string $username,
        string $password
    ): void {
        $user = $this->connection->fetchAssoc(
            'SELECT * FROM users WHERE username = ?',
            [$username]
        );
        if ($user === null) {
            throw new InvalidCredentialsException(
                'User not found’
            );
        }
        // validate password
    }
}
Listing 5-6

The Authentication Class

The Authentication class needs a database connection (see Figure 5-3), in this case represented by a Connection object . It uses the connection to retrieve the user data from the database.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig3_HTML.jpg
Figure 5-3

The Authentication class depends on Connection

There are many problems with this approach. They can be articulated by answering the following questions about this class:
  1. 1.

    Is it important for an authentication mechanism to deal with the exact location of the user data?

    Well, definitely not. The only thing the Authentication class really needs is user data, as an array or preferably an object representing a user. The origin of that data is irrelevant.

     
  1. 2.

    Is it possible to fetch user data from some other place than a database?

    Currently it’s impossible. The Authentication class requires a Connection object, which is a database connection. You can’t use it to retrieve users from, for instance, a text file or from some external web service.

     

Looking at the answers, we have to conclude that both the Single Responsibility principle and the Open/Closed principle have been violated in this class. The Authentication class is not only concerned about the authentication mechanism itself, but also about the actual storage of the user data. Furthermore, it’s impossible to reconfigure the class to look in a different place for user data. The underlying reason for these issues is that the Dependency Inversion principle has been violated too: the Authentication class itself is a high-level abstraction. Nevertheless, it depends on a very low-level concretion: the database connection. This particular dependency makes it impossible for the Authentication class to fetch user data from any other place than the database.

Trying to rephrase what the Authentication class really needs, we realize that it’s not a database connection, but merely something that can provide the user data. Let’s call that thing a “user provider”. The Authentication class doesn’t need to know anything about the actual process of fetching the user data (whether it originates from a database, a text file, or an LDAP server). It only needs the user data.

It’s a good thing for the Authentication class not to care about the origin of the user data itself. All the implementation details about fetching user data can be left out of that class. At once, the class will become highly reusable, because it will be possible for users of the class to implement their own “user providers”.

Refactoring: Abstractions and Concretions Both Depend on Abstractions

Refactoring the high-level Authentication class to make it follow the Dependency Inversion principle means we should first remove the dependency on the low-level Connection class. Then we add a higher-level dependency on something that provides the user data, the UserProvider class (see Listing 5-7).
class Authentication
{
    private $userProvider;
    public function __construct(UserProvider $userProvider)
    {
        $this->userProvider = $userProvider;
    }
    public function checkCredentials(
        string $username,
        string $password
    ): void {
        $user = $this->userProvider->findUser($username);
        if ($user === null) {
            throw new InvalidCredentialsException(
                'User not found’
            );
        }
        // validate password
   }
}
class UserProvider
{
    private $connection;
    public function __construct(Connection $connection)
    {
        $this->connection = $connection;
    }
    public function findUser(string $username): array
    {
        return $this->connection->fetchAssoc(
            'SELECT * FROM users WHERE username = ?',
            [$username]
        );
    }
}
Listing 5-7

Introducing the UserProvider Class

The Authentication class has nothing to do with a database anymore (as depicted in Figure 5-4). Instead, the UserProvider class does everything that’s needed to fetch a user from the database.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig4_HTML.jpg
Figure 5-4

Authentication depends on UserProvider

It’s still not easy to switch between different user provider implementations. The Authentication class depends on the concrete UserProvider class. If anybody wants to fetch their user data from a text file, they’d have to extend this class and override its findUser() method (as is done in Listing 5-8).
class TextFileUserProvider extends UserProvider
{
    public function findUser(string $username): array
    {
        // ...
    }
}
Listing 5-8

Overriding Functionality of UserProvider

They would thereby inherit any behavior that was implemented in the UserProvider class itself and that's not a desirable situation. The solution is to provide an interface, e.g. UserProviderInterface , for any class that wants to be a user provider. Then every class that implements this interface can and should also have a more meaningful name than UserProvider, e.g. MySQLUserProvider (see Listing 5-9).
interface UserProviderInterface
{
    public function findUser(string $username): array;
}
class MySQLUserProvider implements UserProviderInterface
{
    // ...
}
class TextFileUserProvider implements UserProviderInterface
{
    // ...
}
Listing 5-9

Introducing the UserProviderInterface and Some Implementations

And of course we have to change the type of the constructor argument of the Authentication class to UserProviderInterface (see Listing 5-10).
class Authentication
{
    private $userProvider;
    public function __construct(
        UserProviderInterface $userProvider
    ) {
        $this->userProvider = $userProvider;
    }
    // ...
}
Listing 5-10

The Authentication Class Now Accepts a UserProviderInterface

As you can see in the dependency diagram in Figure 5-5, the high-level class Authentication does not depend on low-level, concrete classes like Connection anymore. Instead, it depends on another high-level, abstract thing: UserProviderInterface . Both are conceptually on more or less the same level. Lower-level operations like reading from a file and fetching data from a database are performed by lower-level classes—the concrete user providers. This completely conforms to the Dependency Inversion principle, which states that:

High-level modules should not depend upon low-level modules. Both should depend upon abstractions. 3

../images/471891_1_En_5_Chapter/471891_1_En_5_Fig5_HTML.jpg
Figure 5-5

Authentication depends on UserProviderInterface

A nice side-effect of the changes we made is that the maintainability of the code has greatly improved. When a bug is found in one of the queries used for fetching user data from the database, there’s no need to modify the Authentication class itself anymore. The necessary changes will only occur inside the specific user provider, in this case the MySQLUserProvider . This means that this refactoring has greatly reduced the chance that you will accidentally break the authentication mechanism itself.

Simply Depending on an Interface is not enough

The step from UserProvider to UserProviderInterface was an important one because it helped users of the Authentication class easily switch between user provider implementations. But just adding an interface to a class is not always sufficient to fix all problems related to dependencies.

Consider an alternative version of the UserProviderInterface , shown in Listing 5-11.
interface UserProviderInterface
{
    public function findUser(string $username): array;
    public function getTableName(): string;
}
Listing 5-11

An Alternative UserProviderInterface

This is not a helpful interface at all. It’s an immediate violation of the Liskov Substitution principle. Not all classes that implement this interface will be able to be good substitutes for it. If one implementation doesn’t use a database table for storing user data, it most certainly won’t be able to return a sensible value when someone calls getTableName() on it. But more importantly: the UserProviderInterface mixes different levels of abstraction and combines something high-level like “finding a user” with something low-level like “the name of a database table”.

So even if we would introduce this interface to make the Authentication class depend on an abstraction instead of concretion, that goal won’t be reached. In fact, the Authentication class will still depend on something concrete and low-level: a user provider that is table-based.

Violation: Vendor Lock-In

In this section, we discuss a common violation of the Dependency Inversion principle that is especially relevant to package developers. Say a class needs some way to fire application-wide events. The usual solution for this is to use an event dispatcher (sometimes called “event manager”). The problem is that there are many event dispatchers available, and they all have a slightly different API. For instance, the Symfony EventDispatcherInterface4 looks like the one in Listing 5-12.
interface EventDispatcherInterface
{
    public function dispatch(
        string $eventName,
        Event $event = null
    ): void;
    public function addListener(
        string $eventName,
        callable $listener,
        int $priority = 0
    );
    // ...
}
Listing 5-12

The EventDispatcherInterface

Note that events are supposed to have a name, which is a string (e.g., "new_user"), and when firing (or “dispatching”) the event you can provide an Event object carrying additional contextual data. The event object will be enriched and used as the first argument when the event listener (which can be any PHP callable) gets notified. An example of an event and an event listener class can be found in Listing 5-13.
use SymfonyComponentEventDispatcherEvent;
class NewUserEvent extends Event
{
    private $user;
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    public function getUser(): User
    {
        return $this->user;
    }
}
class EventListener
{
    public function onNewUser(NewUserEvent $event): void
    {
        // ...
    }
}
$eventDispatcher = new EventDispatcher();
$eventDispatcher->addListener(
    'new_user',
    [new EventListener(), 'onNewUser']
);
$user = new User();
$eventDispatcher->dispatch('new_user', new NewUserEvent($user))
Listing 5-13

An Event Class and an Event Listener

An event dispatcher from another framework, Laravel looks like the one in Listing 5-14 (based on version 4.0 of the framework5).
class Dispatcher
{
    public function listen(
        string $event,
        callable $listener,
        int $priority = 0
    ): void {
        ...
    }
    public function fire(string $event, array $payload = []): void
    {
        // ...
    }
    // ...
}
Listing 5-14

The Event Dispatcher from the Laravel Framework

Note that it doesn’t implement an interface. And instead of an event object, the contextual data for events (the “payload”) consist of an array, which will be used as a method argument when a listener gets notified of an event. See Listing 5-15 for an example of how it’s used.
class EventListener
{
    public function onNewUser(User $user)
    {
        // ...
    }
}
$dispatcher = new Dispatcher();
$dispatcher->listen(
    'new_user',
    [new EventListener(), 'onNewUser']
);
$user = new User();
$dispatcher->fire('new_user', [$user]);
Listing 5-15

Using the Laravel Event Dispatcher

It appears that you can do more or less the same things with both event dispatchers, i.e. fire events and listen to them. But the way you do it is different in subtle ways.

Let’s say the package you’re working on contains a UserManager class like the one in Listing 5-16. Using this class you can create new users. Afterwards you want to dispatch an application-wide event, so other parts of the application can respond to the fact that a new user now exists (for instance, maybe new users should receive a welcome email).
use IlluminateEventsDispatcher;
class UserManager
{
    public function create(User $user): void
    {
        // persist the user data
        // ...
        // fire an event: "new_user"
    }
}
Listing 5-16

The UserManager Class

Let’s assume you want to use the package containing the UserManager class in a Laravel application. Laravel already provides an instance of the Dispatcher class in its Inversion of Control (IoC) container. This means you can easily inject it as a constructor argument of the UserManager class , as is done in Listing 5-17.
use IlluminateEventsDispatcher;
class UserManager
{
    private $dispatcher;
    public function __construct(Dispatcher $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }
    public function create(User $user): void
    {
        // ...
        $this->dispatcher->fire('new_user', ['user' => $user]);
    }
}
Listing 5-17

Using the Laravel Event Dispatcher in the UserManager

A couple of weeks later, you start working on a project built with the Symfony framework. You want to reuse the UserManager class, since it offers exactly the functionality that you need, and you install the package containing it inside this new project. Now, a Symfony application also has an event dispatcher readily available in its service container. But this event dispatcher is an instance of EventDispatcherInterface . It’s impossible to use the Symfony event dispatcher as a constructor argument for the UserManager class because the type of the argument wouldn’t match the type of the injected service. You have effectively prevented reuse of the UserManager class .

If you still want to use the UserManager class in a Symfony project, you would need to add an extra dependency on the illuminate/events package to make the Laravel Dispatcher class available in your project. You’d have to configure a service for it, next to the already existing Symfony event dispatcher and end up having two global event dispatchers. Then you’d still need to bridge the gap between the two types of dispatchers, since events fired on the Laravel Dispatcher won’t be fired automatically on the Symfony event dispatcher too. In fact, they even use incompatible types (event objects versus arrays).

The moment you picked the Laravel event dispatcher as the event dispatcher of your choice, you coupled the package to a specific implementation, making it harder or impossible to just use the package in a project that uses a different event dispatcher. Introducing such a dependency to your package is known as “vendor lock-in”; it will only work with third-party code from a specific vendor.

Solution: Add an Abstraction and Remove the Dependency Using Composition

As we discussed earlier, depending on a concrete class can be problematic all by itself because it makes it hard for users to switch between implementations of that dependency. Therefore, we should introduce our own interface, which decouples this class from any concrete event dispatcher implementation. This abstract event dispatcher is not framework-specific, it just offers one method that can be used to dispatch events. Then we can change the UserManager class to only accept an event dispatcher, which is an instance of our very own DispatcherInterface (see Listing 5-18).
interface DispatcherInterface
{
    public function dispatch($eventName, array $context = []);
}
class UserManager
{
    private $dispatcher;
    public function __construct(DispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }
    // ...
}
Listing 5-18

Introducing an Abstraction and Using it in UserManager

The UserManager is now fully decoupled from the framework. It uses its own event dispatcher, which is quite generic and contains the least amount of details possible.

Of course, our DispatcherInterface is not a working event dispatcher itself. We need to bridge the gap between that interface and the concrete event dispatchers from Laravel and Symfony. We can do this using the Adapter pattern6. Using object composition, we can make the Laravel Dispatcher class compatible with our own DispatcherInterface , as shown in Listing 5-19.
use IlluminateEventsDispatcher;
class LaravelDispatcher implements DispatcherInterface
{
    private $dispatcher;
    public function __construct(Dispatcher $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }
    public function dispatch(
        string $eventName,
        array $context = []
    ): void {
        $this->dispatcher->fire(
            $eventName,
            array_values($context)
        );
    }
}
Listing 5-19

Concrete Implementation of the Abstract DispatcherInterface That Uses the Laravel Event Dispatcher

By introducing the DispatcherInterface , we have cleared the way for users of other frameworks to implement their own adapter classes. These adapter classes only have to conform to the public API defined by our DispatcherInterface. Under the hood they can use their own specific type of event dispatcher. For example, the adapter for the Symfony event dispatcher would look like the one shown in Listing 5-20.
use SymfonyComponentEventDispatcherEventDispatcherInterface;
use SymfonyComponentEventDispatcherGenericEvent;
class SymfonyDispatcher implements DispatcherInterface
{
    private $dispatcher;
    public function __construct(
        EventDispatcherInterface $dispatcher
    ) {
        $this->dispatcher = $dispatcher;
    }
    public function dispatch(
        string $eventName,
        array $context = []
    ): void {
        $this->dispatcher->dispatch(
            $eventName,
            new GenericEvent(null, $context)
        );
    }
}
Listing 5-20

Alternative Implementation of DispatcherInterface That Uses the Symfony Event Dispatcher

Before we introduced the DispatcherInterface , the UserManager depended on something concrete—the Laravel-specific implementation of an event dispatcher, as depicted in Figure 5-6.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig6_HTML.jpg
Figure 5-6

The UserManager has a dependency on the concrete Laravel Dispatcher

After we added the DispatcherInterface , the UserManager class now depends on something abstract. In other words, we inverted the dependency direction, which is exactly what the Dependency Inversion principle tells us to do. The resulting dependency diagram is shown in Figure 5-7.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig7_HTML.jpg
Figure 5-7

The UserManager has a dependency on an abstract dispatcher, on which several adapters depend

Packages and the Dependency Inversion Principle

Although the Dependency Inversion principle is a class design principle, it’s all about the relationship between classes. This relationship often crosses the boundaries of a package. Therefore the Dependency Inversion principle resonates strongly at a package level. According to this principle, classes should depend on abstractions, not on concretions. In parallel to this, packages themselves should also depend in the direction of abstractness, as we see in Chapter 11.

Depending on Third-Party Code: Is It Always Bad?

We got rid of vendor lock-in for the UserManager class and we now know the principle by which we can achieve the same thing in many other situations. However, in doing so, there’s a certain cost involved—the cost of defining our own interface and our own adapter implementations. Even if we use dependency inversion for every dependency of every class, there are still other ways in which our code will remain dependent on third-party code.

So the question is, in which cases should we allow ourselves to depend on third-party code and which cases definitely call for dependency inversion?

First, we need to make a distinction between frameworks and libraries. Even though both can be distributed as packages, the difference is most apparent if you consider how they deal with your code. Frameworks follow the Hollywood principle7: “Don’t call us, we’ll call you”. For instance, a web server may forward an HTTP request to your web application, and its framework will analyze the request and call one of your controllers. Once the framework has called your code (also known as “userland code”), you’re free to use anything to accomplish your task, including third-party library code. Figure 5-8 shows how framework, userland, and library code call each other.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig8_HTML.jpg
Figure 5-8

Framework calls userland code, which calls library code

If you’re a package developer who wants to extract part of the userland code and publish it as a package, you should only take out the part of the code that isn’t coupled to the framework. Make sure the code will be useful to all users, no matter what framework they put in front of it. The remaining part of the userland code that is coupled to the framework can be extracted into a framework-specific package, often known as a “bridge” package.

Looking at the framework-independent code that has now been extracted to a package, you can start applying the Dependency Inversion principle there and introduce interfaces and adapter code for concrete classes from libraries that your package uses. Figure 5-9 shows the dependency graph of the resulting packages when the userland code has been extracted and both a framework bridge and a library adapter have been added. The package can be used with framework X, but it should be easy to create another bridge package and make it work with framework Y too. The same goes for library A, for which an adapter is already available. It implements an interface from the package, making it easy to provide a second adapter that will make the package work with library B.
../images/471891_1_En_5_Chapter/471891_1_En_5_Fig9_HTML.jpg
Figure 5-9

The dependency graph after extracting a package and making it independent of the framework and libraries

Not all third-party code requires you to apply the Dependency Inversion principle . If you’d never be allowed to depend on any third-party code directly, you’d have to reinvent everything again and again. In the next section, we enumerate the types of classes that definitely need an interface. What remains are classes that can be used as they are. In practice, I find that these will be classes that do one thing and do it well. Don’t be afraid to depend on those, in particular if their maintainers do a good job in terms of package design.

Examples of third-party code you can depend on as they are can be found in libraries related to:
  • Assertions

  • Reflection

  • Mapping

  • Encoding/decoding

  • Inflection

You’ll likely be able to add some more examples. In practice, you may also decide to depend directly on other concrete third-party code, even though it would call for dependency inversion. You can always add an interface later, and for now enjoy building on top of other people’s ready-to-use building blocks, sacrificing flexibility for an earlier release.

There’s another option that you should think of when you consider using third-party code. Instead of working around their somewhat awkward API, or dealing with their unpredictable release cycle, you may also decide to copy their idea and build your own version of it. For example, if you need an event dispatcher, but none of the popular event dispatcher packages matches your expectations, consider writing one yourself. It isn’t much code anyway, and by doing it, you can design it just the way you want it to be. It’s not always the most efficient thing to do, but at least you have to consider it as a valid option. When reinventing the wheel, sometimes you end up with a better wheel.

When to Publish an Explicit Interface for a Class

In previous chapters, we’ve seen interfaces being used to make classes open for extension and closed for modification. We’ve learned how subclasses can be good substitutes for their interfaces. We’ve also discussed how to split interfaces according to how they are used. And finally we’ve learned how to invert dependency directions toward more abstract things. What we didn’t discuss in detail is which classes actually need an interface. As mentioned several times before, not every class needs an interface. So before we wrap up this chapter and dive into the package design principles, let’s first answer this question: when should you publish an explicit interface for a class? And when is a class without an interface sufficient?

If Not All Public Methods Are Meant to be Used by Regular Clients

A class always has an implicit interface, consisting of all its public methods. This is how the class will be known to other classes that use it. An implicit interface can easily be turned into an explicit one by collecting all those public methods (except for the constructor, which should not be considered a regular method), stripping the method bodies, and copying the remaining method signatures into an interface file (see Listing 5-21).
// the original class with only an implicit interface:
final class EntityManager
{
    public function persist(object $object): void
    {
        // ...
    }
    public function flush(object $object = null): void
    {
        // ...
    }
    public function getConnection(): Connection
    {
        // ...
    }
    public function getCache(): Cache
    {
        // ...
    }
    // and so on
}
// the extracted - explicit - interface:
interface EntityManagerInterface
{
    public function persist(object $object): void;
    public function flush(object $object = null): void;
    public function getConnection(): Connection;
    public function getCache(): Cache;
    // ...
}
Listing 5-21

The Original EntityManager Class and its Extracted Explicit Interface

However, regular clients of EntityManager won’t need access to the internally used Connection or Cache object, which can be retrieved by calling getConnection() or getCache() , respectively. You could even say that the implicit interface of the EntityManager class unnecessarily exposes implementation details and internal data structures to clients.

By copying the signatures of these methods to the newly created EntityManagerInterface , we missed the opportunity to limit the size of the interface as it gets exposed to regular clients. It would be most useful if clients only needed to depend on the methods they use. So the improved EntityManagerInterface should only keep persist() and flush(), as shown in Listing 5-22.
interface EntityManagerInterface
{
    public function persist(object $object);
    public function flush(object $object = null);
}
Listing 5-22

The Improved EntityManagerInterface

We’ve discussed this strategy in more detail in Chapter 4 when we covered the Interface Segregation principle, which tells you not to let clients depend on methods they don’t use (or shouldn’t use!).

If the Class Uses I/O

Whenever a class makes some call that uses I/O (the network, the filesystem, the system’s source of randomness, or the system clock), you should definitely provide an interface for it. The reason being that in a test scenario, you want to replace that class with a test double and you need an interface for creating that test double. An example of a class that uses I/O is the CurlHttpClient in Listing 5-23.
// a class that uses I/O:
final class CurlHttpClient
{
    public function get(string $url): string
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // this call uses the network!
        $result = curl_exec($ch);
        // ...
        return $result;
    }
}
// an explicit interface for HTTP clients like CurlHttpClient
interface HttpClient
{
    public function get(string $url): string;
}
Listing 5-23

The CurlHttpClient and its Interface

If you’d like to know more about using test doubles to replace actual I/O calls, take a look at my article series on “Mocking at Architectural Boundaries”8.

If the Class Depends on Third-Party Code

If there is some third-party code (e.g., from a package you don’t maintain yourself) that is used in your class, it can be wise to isolate the integration of your code with this third-party code and hide the details behind an interface. Good reasons to do so are:
  • The (implicit) interface wouldn’t be how you would’ve designed it yourself.

  • You’re not sure if the package is safe to rely on.

Let’s say you need a diffing tool to calculate the differences between two multi-line strings. There’s an open source package (nicky/funky-diff) that provides more or less what you need, but the API is a bit off. You want a string with pluses and minuses, but the class in this package returns a list of ChunkDiff objects (see Listing 5-24).
class FunkyDiffer
{
    /**
     * @param array $from Lines
     * @param array $to Lines to compare to
     * @return array|ChunkDiff[]
     */
    public function diff(array $from, array $to)
    {
        // ...
    }
}
Listing 5-24

The FunkyDiffer

Besides offering a strange API, the package is being “maintained” by someone you’ve never heard of (and it has 15 open issues and 7 pull requests). So you need to protect the stability of your package and you define your own interface. Then you add an Adapter class9 that implements your interface, yet delegates the work to the FunkyDiffer class, as shown in Listing 5-25.
interface Differ
{
    public function generate(string $from, string $to): string;
}
final class DifferUsesFunkyDiffer implements Differ
{
    private $funkyDiffer;
    public function __construct(FunkyDiffer $funkyDiffer)
    {
        $this->funkyDiffer = $funkyDiffer;
    }
    public function generate(string $from, string $to): string
    {
        return implode(
            " ",
            array_map(
                function (ChunkDiff $chunkDiff) {
                    return $chunkDiff->asString();
                },
                $this->funkyDiffer->diff(
                    explode(" ", $from),
                    explode(" ", $to)
                )
            )
        );
    }
}
Listing 5-25

An Adapter for the FunkyDiffer

The advantage of this approach is that from now on you can always switch to a different library, without changing the bulk of your code. Only the adapter class needs to be rewritten to use that other library.

By the way, a good old Façade10 might be an option here too (see Listing 5-26), since it would hide the use of the third-party implementation. However, due to the lack of an explicit interface, you wouldn’t be able to experiment with alternative implementations. The same goes for the users of your package: they won’t be able to write their own implementation of a “differ”.
final class Differ
{
    public function generate(string $from, string $to): string
    {
        $funkyDiffer = new FunkyDiffer();
        // delegate to FunkyDiffer
    }
}
Listing 5-26

A Façade for FunkyDiffer

If You Want to Introduce an Abstraction for Multiple Specific Things

If you want to treat different, specific classes in some way that is the same for every one of them, you should introduce an interface that covers their common ground. Such an interface is often called an “abstraction,” because it abstracts away the details that don’t matter to the client of that interface. A nice example is the VoterInterface from the Symfony Security component11. Every application has its own authorization logic, but Symfony’s AccessDecisionManager12 doesn’t care about the exact rules. It can deal with any voter you write, as long as it implements VoterInterface and works according to the instructions provided by the documentation of that interface. An example of such an implementation is shown in Listing 5-27.
final class MySpecificVoter implements VoterInterface
{
    public function vote(
        TokenInterface $token,
        $subject,
        array $attributes
    ): int {
        // ...
    }
}
Listing 5-27

Example of a VoterInterface Implementation

In the case of the VoterInterface , the package maintainers serve the users of their package by offering them a way to provide their own authorization rules. But sometimes an abstraction is only there for the code in the package itself. In that case too, don’t hesitate to add it.

If You Foresee That the User Wants to Replace Part of the Object Hierarchy

In most cases, a final class is the best thing you can create. If a user doesn’t like your class, they can simply choose not to use it. However, if you’re building up a hierarchy of objects, you should introduce an interface for every class. That way the user can replace a particular piece of logic somewhere in that hierarchy with their own logic. It will make your code useful in as many situations as possible.

A nice example comes from Tactician13, which offers a command bus implementation.

The package ships with a CommandBus class14 (see Listing 5-28). It’s a class, not an interface, because its implicit interface isn’t larger than its explicit interface would be—the only public method is handle() .
class CommandBus
{
    // ...
    public function __construct(array $middleware)
    {
        // ...
    }
    public function handle($command)
    {
        // ...
    }
    // ...
}
Listing 5-28

The CommandBus Class (Abbreviated)

To set up a working CommandBus instance, you need to instantiate a number of “middleware” classes that all implement the Middleware interface15 (see Listing 5-29). This is an example of an interface that was introduced as an abstraction, allowing the package maintainer to treat multiple specific things in some generic way, as well as to allow users to plug in their own specific implementations.
interface Middleware
{
    public function execute($command, callable $next);
}
Listing 5-29

The Middleware Interface (Abbreviated)

One of these middleware interfaces is the CommandHandlerMiddleware16, which itself needs a “command name extractor,” a “handler locator,” and a “method name inflector”. All of which have a default implementation inside the package (the command name is the class name, the handler for a command is kept in memory, and the handle method is handle plus the name of the command), as shown in Listing 5-30.
$handlerMiddleware = new CommandHandlerMiddleware(
    new ClassNameExtractor(),
    new InMemoryLocator([...]),
    new HandleClassNameInflector()
);
$commandBus = new CommandBus(
    [
        ...,
        $handlerMiddleware,
        ...
    ]
);
Listing 5-30

Setting Up CommandHandlerMiddleware

Each collaborating object that gets injected into CommandHandlerMiddleware can easily be replaced by re-implementing the interfaces of these objects (CommandNameExtractor, HandlerLocator, and MethodNameInflector, respectively). Because CommandHandlerMiddleware depends on interfaces, not on concrete classes, it will remain useful for its users, even if they want to replace part of the built-in logic with their own logic, such as when they would like to use their favorite service locator to retrieve the command handler from.

By the way, adding an interface for those collaborating objects also helps the user decorate existing implementations of the interface by using object composition.

For Everything Else: Stick to a Final Class

If your situation doesn’t match any of the ones described previously, most likely the best thing you can do is not add an interface, and just stick to using a class, preferably a final class . The advantage of marking a class as “final” is that subclassing is no longer an officially supported way of modifying the behavior of a class. This saves you from a lot of trouble later on when you’re changing that class as a package maintainer. You won’t have to worry about users who rely on your class’s internals in some unexpected way.

Classes that almost never need an interface are:
  • Classes that model some concept from your domain (entities and value objects).

  • Classes that otherwise represent stateful objects (as opposed to classes that represent stateless services).

  • Classes that represent a particular piece of business logic, or a calculation.

What these types of classes have in common is that it’s not at all needed nor desirable to swap their implementations out.

Conclusion

As we’ve seen in this chapter, following the Dependency Inversion principle is helpful when others start using your classes. They want your classes to be abstract, only depending on other abstract things, and leaving the details to a couple of small classes with specific responsibilities.

Applying the Dependency Inversion principle in your code will make it easy for users to swap out certain parts of your code with other parts that are tailored to their specific situation. At the same time, your code remains general and abstract and therefore highly reusable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.175.182