Design patterns are something seasoned Application developers incorporates in there App code design to help them down the line in terms of readability, scalability, maintainability and testing. Design patterns follows something called Design principles. These design patterns helps to solve common programming problems with relatively ease and without re-inventing the wheels.
What are software Design Principles?
Software design principles represents a set of guidelines that should be followed while designing software which helps avoiding bad design.
Bad design can be considers as follows:
- Rigidity – Software (code structure) is hard to change because every change affects too many other parts of the system.
- Fragility – Software is fragile, any change you make in it, something else will break (it can be much unexpected).
- Immobility – Software is so interlinked that none of the code can be re-used for other projects.
To overcome these problem the GoF(Gang of Four) book explains SOLID Principles. These principle ensures that the design any application is using does not suffer with any of the above mentioned problems.
SOLID Principle for Design Patterns is an acronym for the following:
- Single Responsibility Principle
- Open and Close Principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
These above five principles also known as SOLID principles are explained in little more details below.
Single Responsibility Principle
A class and/or module should have single responsibility or reason to change.
Responsibility can be single work or task the class/module handles or reason to change anything in the class. If there are more than responsibility or reason to change the class, we must define another class for that reason or responsibility. It should not be like the Swiss army knife which can handle multiple tasks. Our design should be clean enough thus making sure that each class must have one and only one reason to exist in the source.
Open and Close Principle
Software entities like class or modules should be open for extension but closed for modifications.
This can greatly benefit the design of the software as with low modification you don’t have to worry about the changes that can be problematic in future but the software can be extended to add new functionalities and feature with relatively ease.
Derived classes must not change the behavior of their base class. They must only extend the functionality provided by the base class.
A good application design is that take care of the parts of the application that expects frequent changes to them. Change is the only constant in an Application development process. So while design the app we must thrive to minimize the changes in the existing code as much as possible.
Application building blocks like classes and modules should be open for extensions and closed for modifications.
Let’s first take an example that violates the Open/Close Principle. If we are trying to build an application that show shapes that rotates on its axis. Then we can design the app such that:
- We have a base class called “Shape”.
- All the shapes like Rectangle, Square and Circle etc. extends “Shape” class.
- There is a class “ShapeHandler” that is used by the application to show any rectangle or rotate it.
This is obviously very bad design as adding a new shape will break the functionality of “ShapeHandler” class. We need to modify this class as well as add a new class to make the system work again.
Better approach will be to add abstract methods “draw()” and “rotate()” in the base class “Shape”. And make the shapes like Rectangle, Square and Circle etc. implement these methods. Now adding a new shape will only need addition of the shape class, “ShapeHandler” class will not be modified as it will just call “Shape.draw()” or “Shape.rotate()” without considering which type of shape it is dealing with.
Liskov Substitution Principle
This principle states that It is not necessary to create a single hierarchy chain for every class. There may be cases when one class which sounds similar to the other class may in fact does not belong in the same group. For example, In mathematics a square is rectangle thus a square has all the properties that a rectangle has. But in our Object oriented design if we inherit a square from a Rectangle class that it will expose methods like setHeight and setWidth but in reality a square can only has one side parameter for height and width not two.
Same can be said for a Duck class. We cannot inherit a toy duck from a real duck class.
Interface Segregation Principle
Only the methods that must be in an interface should be there. No method that may not be used by the client should be present in the interface.
If we have an interface AnimalAbilities then it should not have a method fly() as not every animal can fly because we don’t want the animals which don’t fly need to implement the fly() method.
Dependency Inversion Principle
- First, High level modules should not depend on low-level modules.
- Second, High level and low level module should only depend on abstraction and not on the details of implementation.
- Third, High level module and low level module should be implemented as per the abstraction not the other way around.
Dependency inversion principle tries to decouple all the modules from each other which enhances the code maintenance, modification and unit testing.
Motivation and violation example
Suppose we are design an Application that reads an Input from the keyboard and shows it on the console. The normal thing to do is, create a class keyboard which will communication between keyboard and a class console whose job would be to communicate with the console. These will be the lower level classes. Then we have one very complex class Publisher while will use keyboard’s instance to read the chars input and console’s instance to write anything onto it. This Publisher class is the higher level class which will depend on the lower classes heavily.
Now what if we need to change the reading from Keyboard to File Handling.
This will create a havoc in the application as now not only we need to change the lower order class from Keyboard to File Handling, we also need to change the Publisher class so that it can use File Handler instance instead of Keyboard. And what if the logic in Publisher is just too complex to comprehend timely and correctly.
Intent and correct behavior
The better approach will be using this layering.
Publisher –> Abstract Reader –> Keyboard.
The keyboard will implement the Abstract Reader interface and Publisher will only know about Abstract Reader. This way even if Keyboard is replaced by File Handler it will still implement the Abstract Reader and no change in the Publisher will ever be required.
Thanks for reading folks, this will be it for the Introduction into design patterns.
PS: SOLID Motivational Posters, by Derick Bailey, is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.