Being SOLID - A glance on the single responsibility principle

23.12.2020 Reading time: 10min

The SOLID principles are a quite popular set of principles in object oriented programming. Originally these principles were introduced by Robert C. Martin, who is also well-known as co-founder of the agile software development manifesto. To me the SOLID principles are a key tool a professional software engineer should know about. Beginning with this article, I want to decrypt this mnemonic acronym over the next five weeks and outline each principle in a compact and illustrative form. Let's start with the first one, namely the Single Responsibility Principle (SRP).

Definition

Although there are multiple formulations in literature, you can narrow it down to

A class should only have one reason to be changed whereby this reason is the classes responsibility.

Source: Robert C. Martin

Example

Before reading more abstract definitions and explanations, let's take a look on concrete examples. Following code snippet shows a class which violates the SRP.

  
    
class InvoiceReporter {

  public function getInvoice(int $month) {
    $invoice = $this->queryDbForInvoice($month);
    $smtpMailer = new SmtpMailer();
    $smtpMailer->sendEmail($invoice);
    return "<h1>Invoice {$invoice->name}</h1>";
  }

  private function queryDbForInvoice(int $month) {
    return DB::table('invoices')->where('month', $month)->first();
  }
}

  

Although this is a very simple class, it has weaknesses regarding the SRP. If you think about the reasons to change this class you might see that there are multiple ones.

  1. First of all the class cares about querying a relational database to get the data. What if we decide to store the invoice data in a CSV file. We would need to change this class.

  2. The invoice will be sent via email whereby the class depends on an SmtpMailer. Maybe the user who consumes this methods wants to get notified via SMS. Then we would need to change this class as well.

  3. Finally, the result returned from the function gets formatted as HTML. It's another responsibility and thus another reason to change for this class. If we want to return for instance JSON instead of HTML, we would need to modify this class as well.

As you could see, even in such a small example it can happen rather easily to introduce multiple responsibilities in one class. You can imagine that in "real world" projects so called "God objects", which get more and more responsibilities over time are just a matter of time if you don't watch out. Let's take a look now how we can do better.

  
    
class InvoiceReporter 
{
  private InvoiceRepository $invoiceRepo;
  private InvoiceNotifier $notifier;
  private InvoiceFormatter $formatter;

  public function __construct(InvoiceRepository $invoiceRepo, InvoiceNotifier $notifier, InvoiceFormatter $formatter)
  {
    $this->invoiceRepo = $invoiceRepo;
    $this->notifier = $notifier;
    $this->formatter = $formatter;
  }

  public function getInvoice(int $month)
  {
    $invoice = $this->invoiceRepo->invoiceForMonth($month);
    $this->notifier->notify($invoice);
    return $this->formatter->output($invoice);
  }

  

As you can see we extracted the three different responsibilities into three classes where each of it cares about just this certain functionality, namely retrieving invoice data from a storage, notifying about the invoice and formatting the output. Now, it's not the responsibility of the InvoiceReporter anymore to know how to fetch data from a relational database or use SMTP to send an email. These are tasks of the injected dependency classes and those are the ones which need to be changed if we want to switch to another data storage or use Mailgun instead of SMTP. The InvoiceReporter should only care about the things which are necessary when getting an invoice.

Conclusion

The SRP is a powerful tool which especially helps you to find out where to change something if applied well. If responsibility borders of classes are well defined, you will have an easier time modifying or adding functionality. The SRP is also connected to other best practice strategies like information hiding. As you could see in our second example, the internals of notifying and formatting were delegated and thus not exposed within the function of retrieving the invoice.

© 2020 Jürgen Ratzenböck, All rights reserved - Privacy Policy