The SOLID Principles in TypeScript: Part Two

The SOLID Principles in TypeScript: Part Two

Welcome back to the second part of our deep-dive into the SOLID principles! As a quick recap, SOLID is an acronym representing a set of five design principles intended to make software designs more understandable, flexible, and maintainable. They were introduced by Robert C. Martin, widely known as "Uncle Bob".

In the first part of this series, we discussed the Single Responsibility Principle (SRP) and the Open-Closed Principle (OCP). We looked at what these principles mean, their pros and cons, and illustrated each with practical TypeScript examples.

In this continuation article, we'll cover the remaining principles: the Liskov Substitution Principle (LSP), Interface Segregation Principle (ISP), and Dependency Inversion Principle (DIP). Just like in part one, we'll provide clear definitions, list the benefits and potential drawbacks, and accompany each principle with a TypeScript example to demonstrate its application.

So, without further ado, let's dive back into the SOLID principles!

Liskov Substitution Principle (LSP)

Definition

The Liskov Substitution Principle (LSP) ensures that a subclass can replace its superclass without affecting the behavior of the program. In other words, objects of a superclass shall be replaceable with objects of a subclass without breaking the application.

Pros

  • It ensures that subclasses remain compatible with the parent class, leading to a more predictable and error-free code.
  • Enhances code maintainability and readability.

Cons

  • Misuse of inheritance can lead to problems. Careful design is needed to avoid complex inheritance hierarchies.

TypeScript Example

// Violating LS
class Bird {
? ? fly() {
? ? ? ? //...
? ? }
}


class Penguin extends Bird {
? ? fly() {
? ? ? ? throw new Error('Penguins can’t fly');
? ? }
}


// Following LSP
class Bird {
? ? fly() {
? ? ? ? //...
? ? }
}


class Penguin {
? ? swim() {
? ? ? ? //...
? ? }
}

P        

In the first scenario, Penguin is a subclass of Bird. However, penguins cannot fly. Therefore, the Penguin class does not fulfill the LSP. In the corrected example, Penguin is no longer a subclass of Bird and does not inherit the fly() method, aligning with the LSP.

Interface Segregation Principle (ISP)

Definition

The Interface Segregation Principle (ISP) states that no client should be forced to depend on interfaces they do not use. Essentially, it’s better to have many smaller, specific interfaces than a single, general-purpose interface.

Pros

  • It promotes high cohesion and low coupling.
  • It results in a system that is easier to refactor, change, and redeploy.

Cons

  • It can lead to a large number of interfaces, which may increase complexity.

TypeScript Example

// Violating IS
interface Worker {
? ? work(): void;
? ? eat(): void;
}


// Following ISP
interface Worker {
? ? work(): void;
}


interface Eater {
? ? eat(): void;
}

P        

In the first scenario, the Worker interface contains eat() method, which might not be necessary for all workers. So it violates ISP. In the second scenario, work() and eat() methods are separated into two interfaces, respecting the ISP.

Dependency Inversion Principle (DIP)

Definition

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules, but both should depend on abstractions. Also, abstractions should not depend on details, but details should depend on abstractions.

Pros

  • It reduces the coupling between the code modules.
  • Enhances code maintainability and flexibility.

Cons

  • It can result in more complexity due to the increased number of abstractions.

TypeScript Example

// Violating DI
class MySQLConnection {
? ? connect() {
? ? ? ? //...
? ? }
}


class PasswordReminder {
? ? dbConnection: MySQLConnection;


? ? constructor(dbConnection: MySQLConnection) {
? ? ? ? this.dbConnection = dbConnection;
? ? }
}


// Following DIP
interface DBConnection {
? ? connect(): void;
}


class MySQLConnection implements DBConnection {
? ? connect() {
? ? ? ? //...
? ? }
}


class PasswordReminder {
? ? dbConnection: DBConnection;


? ? constructor(dbConnection: DBConnection) {
? ? ? ? this.dbConnection = dbConnection;
? ? }
}

P        

In the first example, the PasswordReminder class directly depends on the MySQLConnection class, which violates DIP. In the second example, both PasswordReminder and MySQLConnection depend on the DBConnection interface, which is an abstraction. Thus, it follows the DIP.

By following the SOLID principles, developers can write code that is easier to maintain, understand, and expand. Even though these principles are not a one-size-fits-all solution, and there can be instances where it's more practical not to apply them, they are nonetheless valuable tools for addressing common design problems in object-oriented software design.

要查看或添加评论,请登录

Hitechy的更多文章

社区洞察

其他会员也浏览了