SOLID principles
SOLID is an acronym for 5 main principles in object oriented design.
S - single-responsibility.
It means that class must have only one job, so we decrease potential reasons to change this class. For example image a class that make report and draw charts.
// some pseudo-code
class Report {
createReport() {
let report = {};
// some data aggregation and creation the report object
return report;
}
void drawBarChart(Object report) {
// some code to make svg visualization for bar chart
}
void drawPieChart(Object report) {
// some code to make svg visualization for pie chart
}
}
The minus of Report class in that it make several tasks (processes data and visualize it), so in the future we'll have much reasons for changing class. As follows it's a good idea to carry out the draw methods into separate interfaces.
O - open-closed.
Means that class should be open for extension without breaking existing code, but closed for modification, because it may cause much code refactoring. So, in our example Report class, if in the future we want to change the logic of processing the data and make the report in another form that previous one, it cause many changes in our code. Therefore we must to keep in mind this, and create classes, methods etc. flexible to changes and extensions without breaking existing code.
// some pseudo-code
interface drawMapChart {
drawMapChart(Object report);
}
class Report implements drawPieChart {
// some props
drawMapChart(Object report) {};
}
To connect with previous example imagine that we need to have new visualization, maybe draw our data on map. We create new interface for this reason and implement it to Report class. For mapping vis we use data from our actual report without changing in it, but new interface use this data to put it on map. So we extend Report functionality without modification of existing code and breaking changes.
L - Liskov substitution or LSP.
It is one of the variation of open-closed principle. Objects in app may be replaced by successors without changing app's features. For example, if we have class Human:
// some pseudo-code
class Human {
void sleepDuration() {
println("I need to sleep at least 8 hours");
};
void walk() {...};
}
and class Student:
// some pseudo-code
class Student {
void sleepDuration() {
println("I should't sleep for a long time");
};
}
we say, that the successor class Student violates the LSP, because it has another functionality for sleepDuration method. Therefore, to follow the LSP it's good idea to take off from base class (Human) method sleepDuration and define it in successors, because the sleepDuration mode depends on many factors, such as age, work hours etc.
I - interface segregation.
Clases that implements interface should not use methods they don't need. So, in our Report class we must carry out methods drawBarChart and drawPieChart not to single interface (some Vasualizable interface), but into separate interfaces that draw only one chart for your current needs
// some pseudo-code
interface drawPieChart {
drawPieChart(Object report);
}
class Report implements drawPieChart {
// some props
drawPieChart(Object report) {};
}
Another real life example is when some restaurant use "a dish of the day" in menu. The hungry clients don't care about what they want to eat and use this interface ('dish of the day') for their need. On the othter hand some client wants some specific dish and use this interface from menu.
D - dependency inversion.
Different parts of app must be autonomous and connect to each other by some abstraction - interface. The real life example is when we pay with credit card. The seller don't check your credit card and worry how to transfer money, he only swipes credit card and thus performs the operation. So, the credit card is an abstraction, which connect buy / sale modules.
interface CreditCard {
transferMoney();
approveCardOperation();
}
class Seller implements CreditCard {
// some props
transferMoney() {...};
int sell() {
this.transferMoney()
.then( payment => payment );
}
}
class Buyer implements CreditCard {
// some props
approveCardOperation() {...};
Object buy() {
this.approveCardOperation()
.then( goods => goods );
}
}