Software Design Patterns Implementation
Patterns and SOLID
From Principles to Patterns
You already know the rules of object-oriented programming. The SOLID principles provide a powerful theoretical framework for writing clean, maintainable code. But principles are abstract. To truly master software architecture, you need to see how these principles are applied in the real world. This is where design patterns come in.
Design patterns are not new, groundbreaking inventions. Rather, they are battle-tested, reusable solutions to common problems. Think of SOLID as the 'why'—the guiding philosophy. Design patterns are the 'how'—the concrete blueprints for implementing that philosophy. They are the bridge between abstract principle and working code.
Design patterns are proven solutions to recurring design problems in software development.
The Why Behind the How
Most patterns from the famous Gang of Four book are, at their core, clever applications of one or more SOLID principles. They provide a structure that naturally guides you towards a more modular and robust design. Building software without them is like trying to build a house with custom-cut, interlocking logs. It might work for a small cabin, but if you need to add a new room, you have to re-carve everything. Patterns give you standard bricks and interfaces, allowing you to build, extend, and reconfigure with confidence.
The Single Responsibility Principle (SRP) is a great example. It states that a class should have only one reason to change. Many creational patterns, like the Factory or Builder, exist almost entirely to serve this principle. By delegating the complex task of object creation to a separate 'factory' class, your main application class is no longer responsible for knowing how to construct its dependencies. Its single responsibility becomes using the object, not creating it. This separates concerns beautifully.
Perhaps the most powerful relationship is between the Open/Closed Principle (OCP) and patterns. OCP dictates that software entities should be open for extension but closed for modification. This sounds great, but how do you actually do it? You can't just wish your code into being extendable. Patterns like Strategy, Decorator, and Observer are the practical embodiment of OCP. They create plug-in points in your architecture, allowing you to add new behaviours without touching stable, existing code. This 'design for change' is the hallmark of a mature and scalable system.
Avoiding Architectural Decay
Let's consider a common scenario. You're building an e-commerce platform and need to process payments. The first requirement is just for credit cards. Easy enough. You create a PaymentProcessor class with a method that handles credit card logic. A few months later, the business wants to add PayPal. So, you go back into PaymentProcessor and add an if/else block. Then comes Apple Pay. Then a bank transfer option. Each new feature requires modifying and re-testing the core PaymentProcessor class. This class becomes bloated, fragile, and a nightmare to maintain. It violates both SRP and OCP. This is architectural decay in action.
// Brittle, non-SOLID design
public class PaymentProcessor {
public void processPayment(String type, double amount) {
if (type.equals("credit_card")) {
// Logic to process credit card payment
System.out.println("Processing credit card payment for 💲" + amount);
} else if (type.equals("paypal")) {
// Logic to process PayPal payment
System.out.println("Processing PayPal payment for 💲" + amount);
} // ... and more else-ifs to come!
}
}
To fix this, we can apply the Strategy pattern, which is a perfect expression of the Open/Closed Principle and relies heavily on Dependency Inversion and Interface Segregation. Instead of a monolithic class, we define a PaymentStrategy interface with a single pay method. Then, we create separate classes for each payment method (CreditCardPayment, PayPalPayment) that implement this interface. Our PaymentProcessor no longer contains any specific payment logic. Instead, it holds a reference to a PaymentStrategy object and simply calls its pay method. We 'inject' the desired payment strategy at runtime.
Now, when the business wants to add a new payment method, we don't touch PaymentProcessor at all. We simply create a new class that implements PaymentStrategy. The system is now open for extension (new payment methods) but closed for modification (the core processing logic is stable). We've replaced a brittle if/else chain with a flexible, plug-and-play architecture by using a design pattern that enforces SOLID principles.
Viewing patterns through the lens of SOLID transforms them from recipes you memorise into tools you understand. When you encounter a design problem, instead of asking "Which pattern should I use?", you can ask "Which SOLID principle is being violated?". Often, the answer to the second question will lead you directly to the right pattern.
According to the text, what is the core relationship between SOLID principles and design patterns?
A class that handles user authentication, logs every login attempt, and also sends a welcome email violates which SOLID principle most directly?