SOLID Pattern — Liskov Substitution Principle
Welcome back to the last article on my SOLID pattern research by myself. As software engineers, we strive to write code that is not only functional but also maintainable, scalable, and robust. In object-oriented programming, adhering to solid principles is crucial for achieving these goals. One such principle is the Liskov Substitution Principle (LSP), named after Barbara Liskov, which states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.
Level: None
In TypeScript, a superset of JavaScript, we can leverage the static type system to enforce the Liskov Substitution Principle effectively. Let’s delve into some code examples to understand how this principle works and its implications.
Consider the classic example of shapes, where we have a superclass Shape and subclasses Circle and Square. According to LSP, we should be able to substitute Circle and Square objects wherever Shape objects are expected.
class Shape {
area(): number {
throw new Error("Method not implemented.");
}
}
class Circle extends Shape {
radius: number;
constructor(radius: number) {
super();
this.radius = radius;
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
class Square extends Shape {
sideLength: number;
constructor(sideLength: number) {
super();
this.sideLength = sideLength;
}
area(): number {
return this.sideLength * this.sideLength;
}
}
Now, let’s say we have a function calculateArea that expects an array of shapes and computes the total area:
function calculateArea(shapes: Shape[]): number {
let totalArea = 0;
shapes.forEach(shape => {
totalArea += shape.area();
});
return totalArea;
}
By following LSP, we can confidently pass an array of Circle and Square objects to calculateArea, knowing that it will work as expected:
const shapes: Shape[] = [new Circle(5), new Square(4)];
console.log(calculateArea(shapes)); // Output: 103.67 (approximately)
One advantage of adhering to LSP is the flexibility it provides. We can easily extend our system by adding new subclasses without modifying existing code. However, violating LSP can lead to unexpected behavior and bugs. For example, if we introduce a Triangle class that doesn't override the area method properly, it can break the behavior of calculateArea.
class Triangle extends Shape {
base: number;
height: number;
constructor(base: number, height: number) {
super();
this.base = base;
this.height = height;
}
}
const shapes: Shape[] = [new Circle(5), new Square(4), new Triangle(3, 6)];
console.log(calculateArea(shapes)); // Output: NaN (Not a Number)
Conclusion: Liskov Substitution Principle plays a crucial role in designing robust and maintainable software systems. By adhering to this principle in TypeScript, we can write code that is not only type-safe but also flexible and easily extensible. Remember, honoring LSP leads to cleaner, more reliable code that stands the test of time.
Hope the article was helpful. For the next and last story on this topic a summarized version of all patterns will be writen shortly. If you like what I do feel free to share and follow me! Have a great day!