dev-resources.site
for different kinds of informations.
Golang - How a Chef and Waiter Teach the Single Responsibility Principle
Welcome to the first post in my SOLID principles series for Golang! In this series, Iโll break down each principle of the SOLID design philosophy, helping you write more maintainable and scalable Go applications. Weโre starting with the Single Responsibility Principle (SRP) โ a foundational concept that promotes cleaner code by ensuring every module does just one thing. ๐
๐๏ธ What is the Single Responsibility Principle?
The Single Responsibility Principle states:
A class, module, or function should have only one reason to change.
In simple terms, each component should focus on a single responsibility. If one piece of code is handling multiple tasks, itโs harder to maintain and extend without introducing bugs. Following SRP improves modularity, reusability, and testability.
๐จโ๐ณ A Real-World Example of SRP: The Chef and the Waiter
Imagine a busy restaurant. In this restaurant, two key people are responsible for ensuring customers have a great experience:
- The Chef: Prepares delicious meals.
- The Waiter: Takes orders, serves food, and handles customer requests.
Now, think about what would happen if one person had to do both jobs. If the chef had to stop cooking every time a customer arrived to take an order and serve the meal, it would:
- Slow down the process.
- Cause delays in food preparation.
- Increase the chances of mistakes.
This situation is chaotic and inefficient because the same person is handling multiple unrelated responsibilities.
Applying SRP in the Restaurant
When following the Single Responsibility Principle:
- The Chef focuses only on preparing food.
- The Waiter focuses only on managing orders and serving customers.
By separating these roles, each person can do their job well without unnecessary interruptions. This leads to a smoother, faster, and more enjoyable dining experience for customers. ๐ดโจ
๐ป SRP in Programming
Just like in a restaurant, where each person has a single, focused role, your code should also be structured so that each class or function handles only one responsibility. This makes your application easier to maintain, faster to change, and less prone to errors.
SRP in Action with Golang
Letโs look at an example to see how violating SRP can make code fragile and difficult to manage.
โ Example Violating SRP
Imagine a simple order management system for a coffee shop:
package main
import "fmt"
// Order contains coffee order details.
type Order struct {
CustomerName string
CoffeeType string
Price float64
}
// ProcessOrder performs multiple responsibilities.
func (o *Order) ProcessOrder() {
// Handle payment processing
fmt.Printf("Processing payment of $%.2f for %s\n", o.Price, o.CustomerName)
// Print receipt
fmt.Printf("Receipt:\nCustomer: %s\nCoffee: %s\nAmount: $%.2f\n", o.CustomerName, o.CoffeeType, o.Price)
}
func main() {
order := Order{CustomerName: "John Doe", CoffeeType: "Cappuccino", Price: 4.50}
order.ProcessOrder()
}
Here, the Order
struct is responsible for storing data, processing payments, and printing receipts. This violates SRP because it performs multiple unrelated tasks. Changes to any of these responsibilities will affect ProcessOrder
, making the code less maintainable.
๐ ๏ธ Refactoring for SRP
Letโs separate the responsibilities into distinct components:
package main
import "fmt"
// Order contains coffee order details.
type Order struct {
CustomerName string
CoffeeType string
Price float64
}
// PaymentProcessor handles payment logic.
type PaymentProcessor struct{}
func (p *PaymentProcessor) ProcessPayment(order Order) {
fmt.Printf("Processing payment of $%.2f for %s\n", order.Price, order.CustomerName)
}
// ReceiptPrinter handles receipt printing.
type ReceiptPrinter struct{}
func (r *ReceiptPrinter) PrintReceipt(order Order) {
fmt.Printf("Receipt:\nCustomer: %s\nCoffee: %s\nAmount: $%.2f\n", order.CustomerName, order.CoffeeType, order.Price)
}
func main() {
order := Order{CustomerName: "John Doe", CoffeeType: "Cappuccino", Price: 4.50}
paymentProcessor := PaymentProcessor{}
receiptPrinter := ReceiptPrinter{}
paymentProcessor.ProcessPayment(order)
receiptPrinter.PrintReceipt(order)
}
๐ฏ Benefits of SRP
-
Separation of Concerns:
Order
only stores data,PaymentProcessor
handles payments, andReceiptPrinter
manages receipt generation. -
Better Testability: You can test
PaymentProcessor
andReceiptPrinter
independently. - Easier Maintenance: Changes to receipt formatting wonโt affect payment processing logic.
โ When to Apply SRP
Look for violations like:
- Functions or structs doing multiple unrelated tasks.
- Modules that mix concerns, such as handling both business logic and I/O operations.
โจ Conclusion
By applying the Single Responsibility Principle, your code becomes easier to understand, maintain, and extend. This is just the beginning! Stay tuned for the next post in this series as we explore the "O" in SOLID: the Open/Closed Principle.
You can also check out my other post on Dependency Injection, which is another important technique in OOP.
Happy coding! ๐
Follow me to stay updated with my future posts:
Featured ones: