SOLID series: Liskov Substitution Principle in Go (part 3)

SOLID series: Liskov Substitution Principle in Go (part 3)

SOLID series:

Part 1: Single Responsibility Principle

Part 2: Open-closed Principle

Part 3: Liskov Substitution Principle <- You are here ~~

Part 4: Interface Segregation Principle

Part 5: Dependency Inversion Principle

1. What is Liskov Substitution Principle?

The Liskov Substitution Principle is a fundamental principle of object-oriented programming that emphasizes the importance of designing classes and interfaces in a way that promotes substitutability and preserves the behavior of the base types. Named after Barbara Liskov, the principle serves as a guiding principle for creating well-designed and maintainable code. In this article, we will explore the Liskov Substitution Principle in the context of Go programming language and understand its significance in writing robust and extensible code.

At its core, Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In other words, if a program is designed to work with a base type, it should continue to work correctly when an instance of any derived type is used instead.

2. Liskov Substitution Principle example

Here's an example to illustrate the Liskov Substitution Principle in Go:

Before applying Liskov Substitution Principle:

type Bird struct {
    Name string
}

func (b Bird) Fly() {
    fmt.Println(b.Name, "is flying...")
}

type Penguin struct {
    Bird
}

func (p Penguin) Fly() {
    panic("Penguins cannot fly!")
}

func main() {
    bird := Bird{Name: "Sparrow"}
    bird.Fly() // Output: Sparrow is flying...

    penguin := Penguin{Bird{Name: "Penguin"}}
    penguin.Fly() // Runtime panic: Penguins cannot fly!
}

In the above example, we have a base type Bird with a Fly() method that prints a flying message. We also have a derived type Penguin that embeds Bird but overrides the Fly() method with a panic indicating that penguins cannot fly. This violates Liskov Substitution Principle because the behavior of the derived type does not preserve the behavior of the base type.

After applying Liskov Substitution Principle:

type Bird interface {
    Fly()
}

type NormalBird struct {
    Name string
}

func (b NormalBird) Fly() {
    fmt.Println(b.Name, "is flying...")
}

type Penguin struct {
    Name string
}

func (p Penguin) Fly() {
    fmt.Println(p.Name, "cannot fly!")
}

func main() {
    sparrow := NormalBird{Name: "Sparrow"}
    sparrow.Fly() // Output: Sparrow is flying...

    penguin := Penguin{Name: "Penguin"}
    penguin.Fly() // Output: Penguin cannot fly!
}

In the updated example, we define an interface Bird that declares the Fly() method. The NormalBird struct implements the Bird interface and provides a flying message. The Penguin struct, on the other hand, also implements the Bird interface but gives a message indicating that penguins cannot fly.

By adhering to Liskov Substitution Principle, the code ensures that the derived types (NormalBird and Penguin) can be used interchangeably with the base type (Bird), preserving the behavior of the program. The CalculateTotalArea() function from the previous example can now work seamlessly with any shape implementing the Shape interface without altering its functionality.

3. Conclusion

By adhering to the Liskov Substitution Principle, you create code that is more flexible, modular, and easier to maintain. Here are some benefits and importance of Liskov Substitution Principle in Go programming:

  1. Code Reusability: Liskov Substitution Principle promotes the reuse of code through polymorphism, enabling the usage of base types across different parts of the system. This reusability reduces code duplication and leads to more concise and maintainable codebases.

  2. Modularity and Extensibility: Liskov Substitution Principle encourages the creation of well-defined interfaces and abstraction layers. This allows for easy extension by adding new derived types without affecting existing code that depends on the base types.

  3. Testability: Liskov Substitution Principle promotes the creation of testable code by enabling