S: Single Responsibility Principle
Idea : Do not touch the code due to changes in unrelated things, keep everyone's responsibility separate
In DATABASE Example
- Wrong design - If DB Reader starts performing some transformation, Later of changes in tranformation logic, we have to touch Reader code, which is risky as changes something which is not related to new requirement.
Correct design :
These leads to each class having own specific task and functions, like Separate class for Reader [read() function] and Transformer [performTransform()]
Everyone does its won task with taking other task , independently and secretly(i.e. Encapsulated way).
This principle leads to concept of Containerization of modules as PODs in K8
Example :
A DB Manager may have DBSession Object and DB interactor object.
DBSession just creates a session by loading the conf,
A DB Interactor just takes session object and perform read and write.
Benefits :
1. Maintainability
2. Easy to extend the functionality
3. Loosely coupled interaction, Change in one functionality requires minimum/no changes in others code
O: Open for extension and Closed for Modification
You must design your inheritance structure, Interfaces and classes in such a way that, when you want to add a new functionality.
You should be able to do it without touching an existing implementation.
Open for extension :
Must provide an behavior which can be extended
Closed for modification:
The new extended logic, must not impact the existing code and you should not be required to change the existing parent code, in order to accommodate new extension.
Liskovs Substitute principle:
The new extension must be replaceable/passable in any function, which takes a Base type reference
Ex:
public testBordCapabilities(IBird bird) {
bird.speek();
bird.fly();
}
//but lets says you create a wooden bird, and pass that object, then your wooden bird can also fly,
which is bad, for that now either you have to makes changes in this testFly() to handle instanceOf check
(violation of OCP), or in your woodenBird's fly() method, you have to either do nothing and throw exception, that is also bad.
Solution during the design:
IBird{
speek(); // or any other functionality, which you things all Bird should have
}
IFyableBird implements IBird{
fly()
}
So in this case any existing method (testBirdCapabilities(IBird), which takes reference of IBird, will be able to call only speek() method not fly();
and you can pass your Iflyable object to that method as well, i.e. effectively your object is substitutable to your parent.
and other method, who wants to make use of your flying capabilities, work on your IFlyable reference not on IBird reference
public testFly(IFlyable flyingBird){
flyingBird.fly();
}
Breaking Liskov :
- Throw a new Exception from implemented(Super Class/Interface defined) method from your subclass.
i.e. your subclass is not completely replaceable .
What you do ?
-Create one more Sub Interface, which throws those exception
I: Interface Segregation Principle
Effectively same/taken care, if you had followed Single responsibility and Liskovs Substitute priciple which protect your "Close for modification" property.
But read in a another way.
Read the same IBird, example as above.
If you provide fly() method in IBird, forcing all non flying bird to implement fly() method, by providing some empty implementation or throwing exception, that dont call fly() on non flying Birds.
Then, if your actual flying bird, lets PigeonBird, wants to change the signature of fly(), then it will require change in IBird --> fly() method, this is not OK, but real problem as per "Interface Segregation principle" is, once you change the signature of fly() in Ibird, this will force a change in other non flying birds class signature, say in Penguin, which is already complaining by throwing exception in its unnecessary implementation fly().
Hence "
Clients should not be forced to depend upon interfaces that they do not use "
Here the Client is, Non flying birds like "PenguinBird" and it's force to depend on "fly()" method/interface which it doesn't need".
Solution:
Just like solution for Liskov's, segregate in to single responsibility interfaces.
IBird{
speek(); // or any other functionality, which you things all Bird should have
}
IFyableBird implements IBird{
fly()
}
So, if there is change in IFlyable class's fly() method, other NonFlyableBird which implements IBirds, doesn't need to go through any change.
D: Dependency Inversion Principle
Basically says about, how a High level class , which contains of talks to a low Level class, should not directly depend upon it, rather talk to it via abstraction/interface, so that if new types of low level class can be introduced without any single change in high level class.
Bad design:
Here manager, is highly/directly dependent on low level "Worker" class, if new worker type is added, the mamager also need lots of changes, assume the manager class is too complex with lots of reference of worker instance, you need to change lots of place, may break many functionality, test it again etc etc
// Dependency Inversion Principle - Bad example
class Worker {
public void work() {
// ....working
}
}
class Manager {
Worker worker;
public void setWorker(Worker w) {
worker = w;
}
public void manage() {
worker.work();
}
}
class SuperWorker {
public void work() {
//.... working much more
}
}
// Dependency Inversion Principle - Good example
interface IWorker {
public void work();
}
class Worker implements IWorker{
public void work() {
// ....working
}
}
class SuperWorker implements IWorker{
public void work() {
//.... working much more
}
}
class Manager {
IWorker worker;
public void setWorker(IWorker w) {
worker = w;
}
public void manage() {
worker.work();
}
}