SOLID Principals in Programming
SRP: Single Responsibility Principle
A class should have one and only one reason to change. You should be able to define what an application is doing in one single line.
Program Class:
The
main
method initializes a circle, calculates its area, and then prints it.While it calls both the
CalculateArea
method and theprint
method, it does not perform the actual calculation or printing itself.It adheres to SRP by coordinating the execution of different tasks rather than implementing them directly.
public class Program{
public static void main(String [] args){
Circle circle= new Circle(5);
CalculateArea(circle);
new Printer.print(circle);
}
public void CalculateArea(Circle circle){
Console.WriteLine(circle.getArea());
}
}
Circle Class:
The
Circle
class is responsible for representing a circle and calculating its area.It has attributes related to circles (
Radius
) and methods (getArea
) to perform operations on circles.It adheres to SRP by focusing solely on circle-related operations.
public class Circle{
public Circle (double radius){
Radius=radius;
}
public double Radius { get; set; }
public double getArea(){
return Math.pi * Radius * Radius;
}
}
Printer Class:
The
Printer
class is responsible for printing circles.It has a method (
print
) that takes a circle and prints it.It adheres to SRP by focusing solely on printing circles.
internal Class Printer {
public void print(Circle circle){
Console.Writeline(circle);
}
}
OCP: Open Close Principle
Software Entities should be Open for extension but Closed for Modification.
The below Code does not adhere to OCP as it is open for extension as well as modification. Adding a new Shape will require modification of code which isn't a good practice.
public double Area(Object [] shapes){
double area=0;
foreach(var shape in shapes){
if(shape is Rectangle){
Rectangle rectangle= (Rectangle) shape;
area+= rectangle.Width * rectangle.Height;
}
else{
Circle circle= (Circle) shape;
area+=circle.Radius * circle.Radius * Math.PI;
}
return area;
}
To fully adhere to OCP, you could consider using polymorphism and abstraction to handle different types of shapes without the need for explicit type checking and conditional branches in the Area
method. By utilizing polymorphism and abstraction, the code allows for easy extension by adding new shape types without modifying existing code.
New shapes can be added by simply inheriting from the Shape
class and providing their own implementation of the Area()
method. This allows for the extension of the Area()
method to accommodate new types of shapes without modifying the existing code.
public abstract class Shape {
public abstract double Area();
}
public class Rectangle: Shape {
public double Width {get; set; }
public double Height{get; set; }
public override double Area(){
return Width*Height;
}
}
public class Circle: Shape {
public double Radius {get; set; }
public override double Area(){
return Radius*Radius*Math.PI;
}
}
Since each shape class provides its own implementation of the Area()
method, there's no need for conditional branches or type checking within the Area
method. This means that adding a new shape type doesn't require modifying the existing Area
method. The method is closed for modification in terms of adding new shapes.
public double Area(Shape [] shapes){
double area=0;
foreach(var shape in shapes){
area+=shape.Area();
}
return area;
}
LSP: Liskov Substitution Principle
Subtypes must be substitutable for their base types. Derived or child classes must be substitutable for their base or parent classes.
The methods setWidth
and setHeight
behave differently in the Rectangle
and Square
classes:
class Rectangle {
vois setWidth (double w);
void setHeight (double h);
double getHeight();
double getWidth();
}
class Square{
void setWidth(double w); //Set both height and width to w
void setHeight(double h); //Set both height and width to h
double getHeight();
double getWidth();
}
void test (Rectangle r){
r.setWidth(5);
r.setHeight(4);
assertEquals( 5 * 4, r.getWidth() * r.getHeight());
}
In the Rectangle
class, setWidth
and setHeight
set the width and height independently.
In the Square
class, setWidth
and setHeight
set both the width and height to the same value because a square's sides are always equal.
This difference in behavior violates the Liskov Substitution Principle. When an object of the Rectangle
class is replaced with an object of the Square
class, the client code may expect the setWidth
and setHeight
methods to behave the same way as they do for a rectangle. However, the Square
class changes this behavior, leading to unexpected results.
To adhere to the Liskov Substitution Principle, you should ensure that subclasses can be substituted for their base classes without altering the behavior expected by clients.
ISP: Interface Segregation Principle
The dependency if one class to another one should depend on the smallest possible interface. Client should not be forced to implement interfaces they don't use.
Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule.
Lets see by an example:
Animal
void feed(); //abstract
void groom(); //abstract
Dog implements Animal
void feed(); //implementation
void groom(); //implementation
Tiger implements Animal
void feed(); //implementation
void groom(); //Dummy implementation: Just to keep Complier Happy!!
Now groom is something that is applicable to only domestic or pet animals. So keeping it in Animal is not a good practice as all classes which implement animal must have groom method.
So instead of having one fat interface animal having all features we can create another small interface pet having groom feature. So those classes which implement pet only those classes should provide an implementation of groom.
Animal
void feed(); //abstract
Pet extends Animal
void groom();
Dog extends pet
void feed(); //implementation
void groom(); //implementation
Tiger extends Animal
void feed(); //implementation
DIP: Dependency Inversion Principle
Depend upon abstractions (interfaces) not upon concrete classes.
public class Program{
public static void main(String [] args){
Circle circle= new Circle(5);
CalculateArea(circle);
new Printer.print(circle);
}
public void CalculateArea(Circle circle){
Console.WriteLine(circle.getArea());
}
}
internal Class Printer {
public void print(Circle circle){
Console.Writeline(circle);
}
}
Class Program is dependent on Printer. Now suppose we have a new printer say printer2 which does not have print method or its signature is different. In that case I have to modify my Program class code. But if I create an interface named IPrinter and define a print method in it. Now any new printer will implement IPrinter interface. Now in this case Program is dependent on IPrinter and not on concrete class Printer. So now I have to modify my code only if there is change in interface not everytime when printer changes.
public class Program{
public static void main(String [] args){
Circle circle= new Circle(5);
CalculateArea(circle);
IPrinter printer= new Printer();
printer.Print(circle);
}
public void CalculateArea(Circle circle){
Console.WriteLine(circle.getArea());
}
}
internal interface IPrinter{
void Print(IShape circle);
}
internal Class Printer2 : IPrinter{
public void Print(IShape circle){
Console.Writeline(circle);
}
}
Subscribe to my newsletter
Read articles from Mayank Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by