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

1. The Single Responsibility Principle

Matthias Noback1 
(1)
Zeist, The Netherlands
 
The Single Responsibility principle says that1:

A class should have one, and only one, reason to change.

There is a strange little jump here, from this principle being about “responsibilities” to the explanation being about “reasons to change”. Well, this is not so strange when you think about it—each responsibility is also a reason to change.

A Class with Too Many Responsibilities

Let’s take a look at a concrete, probably recognizable example of a class that is used to send a confirmation to the email address of a new user (see Listing 1-1 and Figure 1-1). It has some dependencies, like a templating engine for rendering the body of the email message, a translator for translating the message’s subject, and a mailer for sending the message. These are all injected by their interface (which is good; see Chapter 5).
class ConfirmationMailMailer
{
    private $templating;
    private $translator;
    private $mailer;
    public function __construct(
        TemplatingEngineInterface $templating,
        TranslatorInterface $translator,
        MailerInterface $mailer
    ) {
        $this->templating = $templating;
        $this->translator = $translator;
        $this->mailer = $mailer;
    }
    public function sendTo(User $user): void
    {
        $message = $this->createMessageFor($user);
        $this->sendMessage($message);
    }
    private function createMessageFor(User $user): Message
    {
        $subject = $this
            ->translator
            ->translate('Confirm your mail address');
        $body = $this
            ->templating
            ->render('confirmationMail.html.tpl', [
                'confirmationCode' => $user->getConfirmationCode()
            ]);
        $message = new Message($subject, $body);
        $message->setTo($user->getEmailAddress());
        return $message;
    }
    private function sendMessage(Message $message): void
    {
        $this->mailer->send($message);
    }
}
Listing 1-1

The ConfirmationMailMailer Class

../images/471891_1_En_1_Chapter/471891_1_En_1_Fig1_HTML.jpg
Figure 1-1

A diagram of the initial situation

Responsibilities Are Reasons to Change

When you talk to someone about this class, you would say that it has two jobs, or two responsibilities—to create a confirmation mail and to send it. These two responsibilities are also its two reasons to change. Whenever the requirements change regarding the creation of the message or regarding the sending of the message, this class will have to be modified. This also means that when either of the responsibilities requires a change, the entire class needs to be opened and modified, while most of it may have nothing to do with the requested change itself.

Since changing existing code is something that needs to be prevented, or at least be confined (see the Introduction), and responsibilities are reasons to change, we should try to minimize the number of responsibilities of each class. This would at the same time minimize the chance that the class has to be opened for modification.

Because a class with no responsibilities is a useless class, the best we can do with regard to minimizing the number of responsibilities is reduce it to one. Hence, the Single Responsibility principle.

Recognizing Violations of the Single Responsibility Principle

This is a list of symptoms of a class that may violate the Single Responsibility principle:
  • The class has many instance variables.

  • The class has many public methods.

  • Each method of the class uses different instance variables.

  • Specific tasks are delegated to private methods.

These are all good reasons to extract so-called “collaborator classes” from the class, thereby delegating some of the class’ responsibilities and making it adhere to the Single Responsibility principle.

Refactoring: Using Collaborator Classes

We now know that the ConfirmationMailMailer does too many things and is therefore a liability. The way we can (and in this case should) refactor the class is by extracting collaborator classes. Since this class is a “mailer,” we let it keep the responsibility of sending the message to the user. But we extract the responsibility of creating the message.

Creating a message is a bit more complicated than a simple object instantiation using the new operator. It even requires several dependencies. This calls for a dedicated “factory” class—the ConfirmationMailFactory class (see Listing 1-2 and Figure 1-2).
class ConfirmationMailMailer
{
    private $confirmationMailFactory;
    private $mailer;
    public function __construct(
        ConfirmationMailFactory $confirmationMailFactory
        MailerInterface $mailer
    ) {
        $this->confirmationMailFactory = $confirmationMailFactory;
        $this->mailer = $mailer;
    }
    public function sendTo(User $user): void
    {
        $message = $this->createMessageFor($user);
        $this->sendMessage($message);
    }
    private function createMessageFor(User $user): Message
    {
        return $this->confirmationMailFactory
                    ->createMessageFor($user);
    }
    private function sendMessage(Message $message): void
    {
        $this->mailer->send($message);
    }
}
class ConfirmationMailFactory
{
    private $templating;
    private $translator;
    public function __construct(
        TemplatingEngineInterface $templating,
        TranslatorInterface $translator
    ) {
        $this->templating = $templating;
        $this->translator = $translator;
    }
    public function createMessageFor(User $user): Message
    {
        /*
         * Create an instance of Message based on the
         * given User
         */
        $message = ...;
        return $message;
    }
}
Listing 1-2

The ConfirmationMailFactory Class

../images/471891_1_En_1_Chapter/471891_1_En_1_Fig2_HTML.jpg
Figure 1-2

Introducing the ConfirmationMailFactory class

Now the creation logic of the confirmation mail has been nicely placed inside ConfirmationMailFactory. It would be even better if an interface was defined for the factory class, but it’s fine for now.

Advantages of Having a Single Responsibility

As a side effect of the refactoring to single responsibilities, both of the classes are easier to test. You can now test both responsibilities separately. The correctness of the created message can be verified by testing the createMessageFor() method of ConfirmationMailFactory. Testing the sendTo() method of ConfirmationMailMailer is also quite easy now, because you can mock up the complete message-creation process and just focus on sending the message.

In general, you will notice that classes with single responsibilities are easier to test. Having a single responsibility will make a class smaller, so you have to write fewer tests to keep that class covered. This will be easier for your mind to grasp. Also, these small classes will have fewer private methods with effects that need to be verified in a unit test.

Finally, smaller classes are also simpler to maintain. It is easier to grasp their purpose and all the implementation details are where they belong: in the classes responsible for them.

Packages and the Single Responsibility Principle

While the Single Responsibility principle should be applied to classes, in a slightly different way it should also be applied to groups of classes (also known as packages). In the context of package design, “having only one reason to change” becomes “being closed against the same kind of changes”. The corresponding package principle is called the Common Closure principle (see Chapter 8).

A somewhat exaggerated example of a package that doesn’t follow this Common Closure principle would be a package that knows how to connect with a MySQL database and knows how to produce HTML pages. Such a package would have too many responsibilities and will be opened (i.e., modified) for all sorts of reasons. The solution for packages like this one is to split them into smaller packages, each with fewer responsibilities, and therefore fewer reasons to change.

There is another interesting similarity between the Single Responsibility principle of class design and the Common Closure principle of package design that I’d like to quickly mention here: following these principles in most cases reduces class (and package) coupling.

When a class has many responsibilities, it is likely to have many dependencies too. It probably gets many objects injected as constructor arguments to be able to fulfill its goal. For example, ConfirmationMailMailer needed a translator service, a templating engine, and a mailer to create and send a confirmation mail. By depending on those objects, it was directly coupled to them. When we applied the Single Responsibility principle and moved the responsibility of creating the message to a new class called ConfirmationMailFactory , we reduced the number of dependencies of ConfirmationMailMailer and thereby reduced its coupling.

The same goes for the Common Closure principle. When a package has many dependencies, it is tightly coupled to each of them, which means that a change in one of the dependencies will likely require a change in the package too. Applying the Common Closure principle to a package means reducing the number of reasons for a package to change. Removing dependencies, or deferring them to other packages, is one way to accomplish this.

Conclusion

Every class has responsibilities, i.e. things to do. Responsibilities are also reasons for change. The Single Responsibility principle tells us to limit the number of responsibilities of each class, in order to minimize the number of reasons for a class to be changed.

Limiting the number of responsibilities usually leads to the extraction of one or more collaborating classes. Each of these classes will have a smaller number of dependencies. This is useful for package development, since every class will be easier to instantiate, test, and use.

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

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