S.O.L.I.D Design Principles
The fundamental thing a programmer must know and USE are the principles of programming that guide a software engineer to write super clean roboust code. Who doesnt want to be told that their code is simply brilliant !
I do!
So lets make our code shine! By begining to learn these idealogies.
- Single Responsibility Principle.
I throughly believe the existance of method should be single purpose only. Its far easier to test and create. There is something about methods that do double work the bothers the life out of me. Some method names like addAndCopy() , printAndSave() are clear indicators that they are doing 2 things and can be easily sperated out. add(), copy() , print(), save(), life is easy for maintainers and writing test cases are a breeze.
Sometimes however the methods are not straight forward, for example makeTea() . It will need a lot of other methods. However, if you can write the name makeTea which is a single responsibility then great! It will be the responsibility of this method to make the tea you can test. And making of tea should be Only known to this method, so whenever you want to change the algorithm of making tea, the only thing that should get effected is makeTea(). That is single responsibility. [Not like your house helper that knows how to do all the things under the method help()!]
This automatically extends to classes and more generally is told with reference to classes.
Let us assume that I am making a simple machine which makes tea, on the press of a button.
Default it gives 50 ml of milk tea. Specially Red Label and with sugar and milk boiled for 10 mins.
This obviously need a thought for future cause the customer obv may ask
Different type of tea, strong , light, medium.
Same for sugar, less , more , no sugar.
Same for milk, less, more, no milk.
Same for water, less, more , no water.
Also in future we might add variety of tea, Green Tea, perhaps even Coffee. Our design must somewhat expect the future changes and deliver .
So we need to keep this in mind and design our system.
Lets go with simple classes first
This way if the company choose to use induction cooker over microwaveOven, then makeTea need not change.
class Sugar implements Ingredients{int quantity;
void heatUp(){
System.out.printin("Im melting"); // turning to lquid etc.
}
}
class Milk implements Ingredients{
int quantity;
void heatUp(){
System.out.printin("Im reducing");
quantity --;
}
}
class Water implements Ingredients{
int quantity;
void heatUp(){
System.out.printin("Im reducing");
quantity --;
}
}
class TeaLeaves implements Ingredients {
int quantity;
void heatUp(){
System.out.printin("Im giving color adding flavour ");
}
}
class Tea implements Item {}
class TeaMaker {
Boiler boiler;
Flask flask;
Filterer filter;
Tea makeTea(Sugar sugar, Milk milk, Water water, TeaLeaves teaLeaves){
flask.addItems( new Ingredients (milk, water, teaLeaves, sugar));
boiler.boil(flask, 100, 5);
List< Ingredients> items = flask. getItems();
Pair<Solid, Liquid> filteredParts = filter(items) ;
throwAwayWaste (filteredParts.left());
return filteredParts. right();
}
strain(List<Ingredients> ingredients ){
Pair<Solid, Liquid> filteredParts =
}
}
public interface Ingredients {isLiquid();isSolid();heatUp();
}public class Filterer {filter(List <Ingredients> ingredients){}}class Boiler {HeatingMachine boilingMachine;Flask flask;Tea boil( List<Ingredients> ingredients , int temperature, int time) {flask.addItems(ingredients);boilingMachine.addItem(ingredients);boilingMachine.setTemp(temperature);boilingMachine.setTime(time);returm boilingMachine.process().getItem();}}interface HeatingMachine {
public void setTemp();
public void setTime();
public void addContainer(Container container);
public Container getContainer();
public void removeContainer();
}
public class MicrowaveOven implements HeatingMachine {
int temprature;
int time;
Container container
setTemp() {
}
setTime() {
}
public void addContainer(Container container) {
this.container = container;
}
Item heat() {
System.out.println("Heating at " + temprature + " for " + time +
"time");
container.getItems()
// for every item call its heat method.
}
public Container removeContainer() {
Container container = container.clone();
this.container = null; // deleteContainer could be another useful method do add.
}
}
for the sake of completeness
public Flask implements Container {
int size;
List < Ingredients > items
setItems(List < Ingredients > items) {
this.items = items;
}
getItems() {
return this.items;
}
}
public interface Container {
public void setItems(List < Item > items);
public List < Item > getItems();
public void removeItems()
}
Here the Class TeaMaker is only responsibel for making tea . not presenting it .
if we need to present in a cup or a tea pot or in a glass . this does not concern TeaMaker, so here this class shows Single Responsibilty. It will only change if a method of making tea changes.
For example you dont want to boil everything at once .
You want to make authentic Indian Chai, then change the makeTea to
Tea tea = boiler.boil(0, water, 0, 0, 0 temprature, time);
Thread.sleep(1000);
boiler.boil(milk, 0, 0, 0, 0 temprature, time, tea);
Thread.sleep(1000); and repeat. you get the point. but only the class Make Tea had to change.
SOLID Principle 2
Open for Extension but closed for Modification .
Basically states that Classes should be open only for Extension . meaning you can take advantage of Interfaces and Subclassing inorder to extend the features. But modification is not allowed.
makeTea is a bad method then
Tea makeTea(Sugar sugar, Milk milk, Water water, TeaLeaves teaLeaves){
flask.addItems( new Ingredients (milk, water, teaLeaves, sugar));
boiler.boil(flask, 100, 5);
List< Ingredients> items = flask. getItems();
Pair<Solid, Liquid> filteredParts = filter(items) ;
throwAwayWaste (filteredParts.left());
return filteredParts. right();
}
Since if you want change the temprature we will have to change this class we cant modify it. So we need to beautify our code.
This is the default Tea that we are trying to make . However customers might want to modify, perhaps boil it for longer .
Perhaps we can do Extend TeaMaker .
public interface TeaMaker.{
public Tea makeTea (Sugar sugar, Milk milk, Water water, TeaLeaves teaLeaves);
}
public class StrongTeaMaker implements TeaMaker {
makeTea // boils the tea for 20 mins at low temp.
}
public class DefaultTeaMaker implements TeaMaker {
makeTea // boils the tea for10 mins at 100 C
}
public class LightTeaMaker implements TeaMaker {
makeTea // boils the tea for5 mins at high temp.
}
This is one of the solutions to solve our Open and closed principle.
There is a lot of repeatitive code , we can just use factory method pattern. Initialize the object based on the need.
TeaType teaType;
TeaSize teaSize;
Milk milk;
Water water;
Sugar sugar ;
TeaLeaves teaLeaves;
int temp;
int time;
TeaMaker(TeaType teaType, TeaSize teaSize){
switch(teaType){
case TeaType.STRONG:
switch (teaSize):
// initialize milk, water , temp, etc.
break;
case TeaType.LITE:
switch (teaSize):
// initialize milk, water , temp, etc.
break;
...
}
}
Tea makeTea(Sugar sugar, Milk milk, Water water, TeaLeaves teaLeaves){
flask.addItems( new Ingredients (milk, water, teaLeaves, sugar));
boiler.boil(flask, temp, time);
List< Ingredients> items = flask. getItems();
Pair<Solid, Liquid> filteredParts = filter(items) ;
throwAwayWaste (filteredParts.left());
return filteredParts. right();
}
The L of Solid
Liskovs Substitution Principle .
It simply says that when u substitute a derived class object in place of its parent, then the overall behvaiour should be as expected. For example no new throws() .. should come .
Example
public interface Bike{
startEngine();
stopEngine();
}
public BiCyle implements Bike {
startEngine(){
throw(new Exception ("No Engine in BiCycle"));
}
}
This example is based on the video [This channel has great content for interview prep]
The client will not know about this exception as it only cared about the interface and this hence breaks protocol. Always remember it is only the interface the clients look at so if it aint throwing exception the subclasses should not. Therefore implement them accordingly to avoid bugs.
Comments
Post a Comment