Xing \ Creative \ Coding

Web Application Software Development

The examples below are in PHP, but a knowledge of any OOP language should suffice in understanding the examples.

Agile programmers like to throw around their acronyms. Sometimes it’s just to make them look smart, but the better purpose is so that we can communicate a plethora of information without taking up a lot of time. SOLID programming is one of those acronyms that contains quite a few principles about code design that are foundational to good OOP programming.

Single Responsibility Principle

To start us off, the first letter “S” stands for Single Responsibility Principle. Perhaps the easiest way to understand it is by knowing its opposite. The opposite of this principle would be the god-object, which does everything. A god-object might format your strings, open the connection to the database, query for results, and handle business logic. The god-object is a jack of all trades and master of none. The Single Responsibility Principle in contrast states that your objects should only do one thing (and, if I might add, do it well).

Why is this important?

Let’s say that you have an e-mailing class MailMessage in your open source project. The class is, initially, just an OOP wrapper for the mail() method in PHP with methods like addRecipient(name,email), which streamline the composition of the e-mail. However, you start to realize that various platforms and requirements are going to require very different ways of sending e-mail:

  • Unix sendmail requires headers to be separated with \n.
  • Windows MTAs can’t handle the name in the to parameter for the mail() method.
  • Some people will likely need to send e-mail via SMTP.
  • It would be useful for unit tests to mock the sending process but not actually send e-mail.

So, in thinking about this problem, we have discovered two responsibilities. The first responsibility is the interface by which a user builds the e-mail, and the second responsibility is how that message is sent. Thinking of the Single Responsibility Principle we decide that we are going to separate these responsibilities into two separate classes: MailMessage and MailSender.

The MailMessage class is how the user builds the e-mail. This is unlikely to ever need to change. So, we’ll build this as a concrete class. However, we’ve already identified that our other responsibility–how to send the e-mail–is likely to change a lot. Just by separating these responsibilities, and knowing where things are likely to change, we’ve somewhat stumbled into the Open/Closed Principle (the “O” in SOLID).

Open/Closed Principle

If you write the MailMessage class with \r\n between headers, and someone using sendmail needs \n, they are going to have to directly modify your MailMessage class. The Open/Closed Principle reminds us that we want to avoid that scenario, because once they modify your class they can’t update your library to the latest without breaking their implementation. After we complete our MailMessage and MailSender classes they should be “closed to modification,” but “open to extension”. Anything that may need to change for different implementations should have an avenue of extension that does not require the original classes to be modified.

The obvious avenue of “open to extension” is inheritance. If you can inherit the class and modify what you need to modify, you can do whatever you want. However, that has problems of its own when the parent class’s behavior changes unexpectedly, so we don’t want to force users’ to extend our class in order to modify it’s behavior. In this case, we already know where the extension points need to be, which is that we need multiple instances of MailSender. To give an easy method of extension without modification, we are going to use another SOLID principle–out of order this time–Dependency Inversion Principle

Dependency Inversion Principle

The Dependency Inversion Principle states that our high-level, library class MailMessage should not depend on the low-level implementation details of our MailSender class. Instead, both the library and the client code should depend on abstractions. So, to avoid implementation details in our high-level class, we create an interface: IMailSender. Then, both our MailMessage class and our implementations will depend on this abstraction but not have any knowledge about each other. Here’s a rough concept of what that would look like:


interface IMailSender {
    /**
      * Returns true on success, or false on failure
      * @return bool
      **/
    public function send( $to, $subject, $message, array $headers );
}

Then, in our MailMessage class, we implement dependency-injection:


class MailMessage {
    private $_sender;
    public function __construct( IMailSender $sender ) {
        $this->_sender = $sender;
    }
}

So, that takes care of the SO**D, but unless you’re British, SOD isn’t a useful acronym. So, what about the “L”, and the “I”?

Now that we have an interface IMailSender, and we have dependencies in our MailMessage, we can discuss these two items.

Liskov Substitution Principle

I put a comment in the interface that it returns true on success and false on failure. Any implementation and any subtype to those implementations should be able to be “substituted” into the MailMessage class without altering the correctness of the MailMessage class. In this simplistic example, if an implementation started returning “success”/”failure” instead of true/false, it would alter the behavior of the program, violate this principle, and break the MailMessage implementation.

Now, this is where I stick in my shameless plug about strongly-typed languages being better, because in a strongly typed language like C#, trying to return a string from a bool method would cause a compile-time failure. However, PhpStorm and possibly some other IDEs do try to give intellisense that communicates the same problems when you utilize DocBlocks.

Interface Segregation Principle

In a nutshell, interfaces should be small and not have a lot of methods that an implementation has to define (or throw NotImplementedException for). In our case, we’ve done this. An implementation will only need to implement the send() method. If we had added a method setHeaderDelimiter() to our interface, this might be good for the default mail() implementations, but would not be needed or used by the SMTP implementations which would always use \r\n. Those implementations would have no need for such a method, so it’s better to leave it out and handle such concerns in the objects they pertain to.

Conclusion

It’s not the end of the world if you don’t follow all of these principles all of the time. These principles can also add to the complexity of your application, so if You-Aren’t-Going-to-Need-It (YAGNI), it may not be worth the time investment. However, it is a good idea to fully understand the principles so that you know which ones you are breaking, why you are breaking them, and possible code smells that can result as the application changes.

March 24th, 2015

Posted In: Software Design

Tags: ,

Leave a Reply