On 24 and 25 January it was time again for PHPBenelux the 5th version no less. Two days full of PHP talks that started Friday morning with tutorials. My choice of tutorial was “Design patterns workshop” by Brandon Savage.
Before the tutorial really started about specific design patterns themselves we went back to the basics and got a explanation of SOLID. Because SOLID is the base of most of the design patterns and Object Orientated Programming. SOLID stands for five principles that should help with the making of better, more testable and easier extendable code.
Single responsibility principle(SRP)
The “Single responsibility principle” states that every class should just have one responsibility and should handle that responsibility completely internal.
You can think here for example of a database. When you create a database class that connects to the database you might feel like creating a function in the class as well that creates a query for you or parses the results from that query. However SRP states that this is wrong the query functionality should have its own class that of course does not know anything about creating a connection.
The reasoning behind this is of course that if anything changes in the connection (you switch from MySQL to SQL) you should be able to change just the connection without changing the query generation.
Maybe something you would not think about but the usage of (too much) New is wrong as well as the creation of a new object could be seen as a task as well so should have its own class.
“Open/Closed principle” states that entities within software should be open for extending but closed for modification. This principle has led to two interpretations.
Meyer's Open/Closed principle
The idea behind this Meyer's open/closed principle is that once a class is made it should not be changed anymore except to fix bug. Any extra functionality should result in a new class that can extend from the original class and use its code. In this new class you can change the interface and functionality as long as you don't modify the original code.
Polymorphic Open/Closed principle.
The polymorphic open/closed principle on the other hand states that the interface should be closed instead of the class. This means that once you created a class its OK to change any code however the interface should not be touched, So when you inherit a Abstract class you can only use the public functions from that class, adding any new functions should be private or protected because all talking with the class should be done trough the interface that was created before. The reason of course is that code that uses that class should never be changed even if functionality changes as long as the interface is the same it means that that code can always expect the same type of calls and return values.
Liskov substitution principle(KSP)
“when S is a subtype of T than all objects from type T can be replaced by objects of type S”.
Imagine we have 3 classes:
- Animal Class (T)
- Dog class that extends from Animal (S)
- Cat class that extends from Animal (S)
This means that everywhere we use Animal we can also use Dog or Cat. The same applies the other way around of course everywhere where we use Cat we can also use Dog or Animal.
Of course it's important to note here that this works closely together with the Open/Closed principle. For example you should not create a barks function in Dog and a mauws function in cat, these functions cannot/should not exist in the Animal class as not every animal makes those sounds. But if you would provide them in Dog and Cat you would change the interface.
It is better to create a makesSound function in the Animal class and implement them in the Dog and Cat class with their specific sounds.
Interface segregation principle(ISP)
To explain the Interface segregation principle the Xerox example was used in the tutorial.
The Xerox problem was as follows:
Xerox created a new printer system that could do a wide range of tasks for example print, staple, fax and copy. However how more functionality grew so did the software supporting it, the changing of the software took longer every new function and also took longer to distribute even for the smallest of changes.
The problem for Xerox was that there was one big God class that handled all the tasks for the printer but because it had all the tasks it meant that a staple job had knowledge of the printer task and vise-versa.
ISP states that this is a problem as no client should have knowledge of functionality it does not use. The solution was to extract the functionality to new job classes that just extended from the jobclass and just implemented what they needed resulting in classes like printJob and stapleJob.
This made it easier for the developers in Xerox to add functionality or fix errors or just make any changes in the code as the functionality was separated. Not only it was easier and faster for the developers to changes things it was also faster to distributed these changes as only the changed functionality could be send to the printers instead of the one mega class.
Dependency inversion principles(DIP)
Dependency inversion principle states:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
To explain this we can use the Xerox example again and use the printer. Imagine the first version of the printer can only print to handle this we have a printJob. To execute that job we also have a jobqueue that has a addJob(printJob $job) function. This works perfectly and we can print.
Now however we get a new version of the printer and the functionality to fax is added. You can see the problem ad this moment we have to change code we should change the addJob function or add an addFaxJob(faxJob $job) function to our jobqueue class.
Instead we should have created a jobInteface from the start and extend the printJob from this. The addJob should then have been addJob(jobInterface $job). Now when we would have added the faxJob and let it extend from the jobInterface we wont have to change the jobqueue. And any future functionality would be as easy to add.
But don't forget the open/closed principle, if you change the interface in any of the jobs it would mean that the code that uses that job still has to know what kind of job it is otherwise it can't access that functionality.