Sitemap

The Open-Closed Principle: Building Extensible Software in Go

3 min readJan 20, 2025

Introduction

In software engineering, writing maintainable code is just as important as writing functional code. Today, let’s dive into one of the most fundamental SOLID principles — the Open-Closed Principle (OCP), with practical examples in Go.

Press enter or click to view image in full size
Open-Closed Principle (OCP)

Understanding the Open-Closed Principle

The Open-Closed Principle states that software entities should be:

  • Open for extension
  • Closed for modification

Think of it like building with LEGO blocks — you don’t break existing blocks to create new structures; you add new blocks instead.

The Problem: Rigid Payment Processing

Let’s look at a common anti-pattern:

func processPayment(paymentType string, amount float64) {
if paymentType == "CreditCard" {
fmt.Println("Processing credit card payment for:", amount)
} else if paymentType == "PayPal" {
fmt.Println("Processing PayPal payment for:", amount)
} else if paymentType == "Bitcoin" {
fmt.Println("Processing Bitcoin payment for:", amount)
} else {
fmt.Println("Unsupported payment type")
}
}

Problems with this approach:

  1. Adding new payment methods requires modifying existing code
  2. Risk of breaking existing functionality
  3. Violates the single responsibility principle
  4. Hard to test and maintain

The Solution: Embracing OCP

Here’s how we can refactor using OCP:

// PaymentProcessor interface defines the contract
type PaymentProcessor interface {
Process(amount float64)
}

// CreditCardProcessor implements PaymentProcessor
type CreditCardProcessor struct{}

func (c CreditCardProcessor) Process(amount float64) {
fmt.Println("Processing credit card payment for:", amount)
}

// PayPalProcessor implements PaymentProcessor
type PayPalProcessor struct{}

func (p PayPalProcessor) Process(amount float64) {
fmt.Println("Processing PayPal payment for:", amount)
}

// BitcoinProcessor implements PaymentProcessor
type BitcoinProcessor struct{}

func (b BitcoinProcessor) Process(amount float64) {
fmt.Println("Processing Bitcoin payment for:", amount)
}

// Generic payment processing function
func processPayment(processor PaymentProcessor, amount float64) {
processor.Process(amount)
}

Benefits of This Approach

  1. Extensibility: Adding new payment methods is as simple as creating a new struct that implements the PaymentProcessor interface.
  2. Maintainability: Existing code remains untouched when adding new payment methods.
  3. Testability: Each processor can be tested in isolation.
  4. Flexibility: Easy to swap implementations without changing the core logic.

Adding a New Payment Method

Want to add cryptocurrency support? No problem:

// New payment method - no existing code modified!
type CryptoProcessor struct{}

func (cr CryptoProcessor) Process(amount float64) {
fmt.Println("Processing cryptocurrency payment for:", amount)
}

Real-World Usage

func main() {
// Create processors
creditCard := CreditCardProcessor{}
paypal := PayPalProcessor{}
crypto := CryptoProcessor{}

// Process payments
processPayment(creditCard, 100.00)
processPayment(paypal, 50.00)
processPayment(crypto, 75.00)
}

Best Practices

  1. Design interfaces before implementations
  2. Keep interfaces small and focused
  3. Use composition over inheritance
  4. Think about future extensions while designing
  5. Follow the Interface Segregation Principle

Conclusion

The Open-Closed Principle isn’t just a theoretical concept — it’s a practical tool for building maintainable software. By designing our code to be open for extension but closed for modification, we create systems that are:

  • Easier to maintain
  • More flexible
  • More testable
  • More resilient to change

Remember: Good software design is about managing change effectively. The Open-Closed Principle is your ally in this endeavor.

--

--

Vatsal
Vatsal

Written by Vatsal

Hi 👋, I’m Vatsal. A passionate Software Developer | Fun fact: Funny, Anime-addict, Binge Watcher. | Follow Me on GitHub: https://github.com/backendArchitect

No responses yet