Go lang: From 0 to Employed

Igor Carvalho
8 min readJun 5, 2023

--

S.O.L.I.D principles :: OCP

If you skipped `S.O.L.I.D principles :: SRP` go back here!

Open/Closed Principle in Golang

Embark on a hands-on journey exploring the Open/Closed Principle (OCP) in GoLang. This guide is designed to give you a straight-forward understanding of interfaces and composition with examples that go from basic violations to actual OCP applications. It’s about giving you the right tools to write better GoLang code in less time. Whether you’re fixing bugs or starting from scratch, this is your roadmap to efficient coding. Ready to power up your GoLang skills? Let’s get started!

Attention: For who is this guide?

This guide is part of a web series intended for individuals who are already proficient in a programming language and are looking to learn GoLang in a simple and fast manner. Therefore, I cover various aspects of the language directly and succinctly, aiming to provide the necessary material for a smooth career transition from other languages to Go. The focus is on supplying ample learning material and support, enabling developers unfamiliar with Go to start working with GoLang as quickly as possible.

Index:

  • Introduction
  • Explanation
    — Interfaces
    — Composition
  • Examples
    — Example 1: Basic OCP Violation
    — Example 2: Applying OCP with Interfaces
    — Example 3: Applying OCP with Composition
  • Advantages and Disadvantages
    — Advantages
    — Disadvantages
  • Real-world Applications
  • Additional Tips
  • Conclusion
  • Summary to Recap

Introduction

In the world of software development, adhering to best practices and design principles can significantly improve the quality, maintainability, and scalability of your code. The SOLID principles, introduced by Robert C. Martin, are a collection of five design principles aimed at achieving these goals. In this blog post, we will focus on the Open/Closed Principle (OCP), one of the core SOLID principles, and explore its application in Golang projects.

The Open/Closed Principle provides guidance on creating software components that are open for extension but closed for modification. By following this principle, you can develop more robust and flexible software that can easily adapt to new requirements or changes without modifying existing code. In the coming sections, we will dive into the details of OCP, discuss examples, advantages and disadvantages, real-world applications, and provide additional tips for applying this principle in your Golang projects.

Explanation

The Open/Closed Principle (OCP) states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In simpler terms, once a software component is completed and tested, you should be able to extend its behavior without altering the existing code. This principle encourages developers to create flexible and modular software that can easily adapt to new requirements or changes.

In Golang, OCP can be applied to both packages and functions. Although Golang does not have classes in the traditional object-oriented sense, it has structs and interfaces, which can be used to achieve the desired level of abstraction and extensibility.

Interfaces
Interfaces play a crucial role in applying OCP in Golang. An interface defines a contract or a set of methods that a type must implement, allowing multiple types to be used interchangeably while adhering to the same contract. By using interfaces, you can extend the functionality of your code without altering the existing implementation.

Composition
Another way to apply OCP in Golang is by using composition. Composition allows you to build complex types by combining smaller, more focused types, each responsible for a specific functionality. By composing smaller types, you can extend the behavior of your code without modifying the existing components.

In the next section, we will look at examples that demonstrate how to apply the Open/Closed Principle in Golang, which will further illustrate the benefits and practicality of this design principle.

Examples
Let’s dive into some examples to better understand how to apply the Open/Closed Principle in Golang.

Example 1: Basic OCP Violation
Suppose you have a function that calculates the area of different shapes, like rectangles and circles. A naive implementation might look like this:

type Shape struct {
Type string
Width float64
Height float64
Radius float64
}
func CalculateArea(s Shape) float64 {
if s.Type == "rectangle" {
return s.Width * s.Height
} else if s.Type == "circle" {
return math.Pi * s.Radius * s.Radius
}
return 0
}

This implementation violates the Open/Closed Principle because adding support for a new shape would require modifying the Area function, potentially introducing bugs or breaking existing functionality.

Example 2: Applying OCP with Interfaces
To apply OCP, we can refactor the code using interfaces. First, define an interface for shapes that require an area calculation:

type Shape interface {
Area() float64
}

Next, create structs for each shape type (e.g., Rectangle and Circle) and implement the Area method for each:

type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}

func CalculateArea(shape Shape) float64 {
return shape.Area()
}

Now, the Area method is part of each shape’s implementation, adhering to the Open/Closed Principle. To add a new shape, you simply need to create a new struct and implement the Shape interface, without modifying any existing code.

Example 3: Applying OCP with Composition
In this example, we have a PaymentProcessor that processes payments using different payment gateways, such as Stripe and PayPal. Instead of hardcoding the payment gateway logic inside the PaymentProcessor, we can apply OCP using composition.

First, define a PaymentGateway interface:

type PaymentGateway interface {
ProcessPayment(amount float64) error
}

Next, create structs for each payment gateway (e.g., StripeGateway and PayPalGateway) and implement the ProcessPayment method for each:

type StripeGateway struct {
APIKey string
}
func (s StripeGateway) ProcessPayment(amount float64) error {
// Implement Stripe payment processing
}
type PayPalGateway struct {
ClientID string
ClientSecret string
}
func (p PayPalGateway) ProcessPayment(amount float64) error {
// Implement PayPal payment processing
}

Finally, compose the PaymentProcessor using the PaymentGateway interface:

type PaymentProcessor struct {
Gateway PaymentGateway
}
func (p PaymentProcessor) ProcessPayment(amount float64) error {
return p.Gateway.ProcessPayment(amount)
}

Now, the PaymentProcessor is open for extension and closed for modification. To add a new payment gateway, you only need to create a new struct implementing the PaymentGateway interface and pass it to the PaymentProcessor. No modification of the existing code is required.

Advantages and Disadvantages

By applying the Open/Closed Principle in Golang, you can achieve numerous benefits. However, there are also some potential drawbacks to consider. Let’s examine the advantages and disadvantages of following the Open/Closed Principle in Golang.
Advantages

  • Flexibility: Applying OCP promotes flexibility by allowing you to extend your code without modifying existing functionality. This makes it easier to accommodate new requirements or changes without affecting the current implementation.
  • Maintainability: Following OCP results in more maintainable code, as you are less likely to introduce bugs or break existing functionality when making changes or adding new features.
  • Testability: Code that adheres to OCP is generally easier to test, as each component can be tested in isolation, making it simpler to identify and fix issues.
  • Reusability: OCP encourages creating modular components with well-defined responsibilities, which can be easily reused in different parts of your application or across different projects.

Disadvantages

  • Increased complexity: Applying OCP can sometimes result in increased complexity due to the introduction of additional abstractions (e.g., interfaces) and increased number of components.
  • Premature abstraction: There is a risk of creating unnecessary abstractions when trying to adhere to OCP, which can lead to over-engineering or a more complicated design than necessary.
  • Performance trade-offs: In some cases, following OCP may lead to performance trade-offs due to the use of interfaces or additional layers of abstraction.

Despite these potential drawbacks, the benefits of adhering to the Open/Closed Principle typically outweigh the disadvantages, especially when building complex, long-lived applications where maintainability, flexibility, and extensibility are crucial factors.

Real-world Applications

The Open/Closed Principle is widely used in real-world applications, including Golang projects. By adhering to OCP, developers can create more maintainable, flexible, and extensible code, which is particularly important in complex, long-lived projects. Some real-world applications of OCP in Golang include:

  • Go standard library: The Go standard library is a prime example of OCP in action. Many packages in the standard library, such as io.Reader and io.Writer interfaces, follow the Open/Closed Principle, allowing developers to extend their functionality without modifying the existing code.
  • Middleware pattern: The middleware pattern, commonly used in web development, adheres to OCP. Middleware components can be added or removed without modifying the core application, enabling developers to create modular and extensible applications.
  • Dependency injection: Dependency injection is a technique used to promote loose coupling and adherence to OCP. By injecting dependencies (e.g., interfaces) into components, you can easily extend or modify the behavior of your application without altering existing code.
  • Popular open-source projects: Many popular open-source Golang projects, such as Gin, Echo, and GORM, follow the Open/Closed Principle, allowing developers to easily extend and customize their functionality.

In each of these examples, the Open/Closed Principle helps create more maintainable, flexible, and extensible code, which is essential for the long-term success of a software project.

Additional Tips

Here are some additional tips to help you effectively apply the Open/Closed Principle in your Golang projects:

  • Identify areas for extension: When designing your application, think about the parts that are most likely to change or require extensions in the future. Focus on applying OCP in these areas to make your code more adaptable to new requirements.
  • Leverage interfaces: Use interfaces to define contracts for your components, making it easy to extend their functionality without modifying existing code. This helps you adhere to OCP and promotes loose coupling between components.
  • Embrace composition: Favor composition over inheritance when designing your components, as it allows for greater flexibility and easier adherence to OCP.
  • Keep it simple: Avoid over-engineering or creating unnecessary abstractions when applying OCP. Keep your design as simple as possible while still providing the necessary level of extensibility.
  • Don’t be afraid to refactor: If you discover that your existing code does not adhere to OCP and needs to be extended, don’t be afraid to refactor. Refactoring your code to follow OCP can lead to long-term benefits in terms of maintainability and flexibility.
  • Write tests: Writing tests for your components helps ensure that they adhere to their contracts and function correctly. This is especially important when following OCP, as it helps you verify that your code can be extended without breaking existing functionality.

By keeping these tips in mind and applying the Open/Closed Principle in your Golang projects, you can create more maintainable, flexible, and extensible code that is better equipped to handle the ever-changing demands of software development.

Conclusion

The Open/Closed Principle is an essential design principle in software development, promoting code that is both extensible and maintainable. By adhering to OCP, you can create Golang applications that are easier to maintain, test, and scale over time. The use of interfaces and composition in Golang allows developers to apply OCP effectively, leading to more modular and reusable code.

In this blog post, we have explored the Open/Closed Principle in Golang, discussing its explanation, examples, advantages, disadvantages, real-world applications, and additional tips for applying the concept. By following the guidance provided in this post, you can create Golang projects that are better equipped to handle changes and new requirements, ensuring the long-term success of your software.

Summary to Recap

  • The Open/Closed Principle (OCP) states that software entities should be open for extension but closed for modification.
  • OCP promotes maintainability, flexibility, and extensibility in software development.
  • In Golang, you can apply OCP using interfaces and composition.
  • Adhering to OCP has several advantages, including improved maintainability, testability, and reusability, but can also lead to increased complexity or performance trade-offs in some cases.
  • The Open/Closed Principle is widely used in real-world Golang applications, including the Go standard library, middleware pattern, dependency injection, and popular open-source projects.
  • To effectively apply OCP in Golang, consider using interfaces, embracing composition, identifying areas for extension, keeping the design simple, refactoring when necessary, and writing tests.

--

--