Hello! Welcome to the final lesson in our module on Foundational Design Patterns.
In our previous lessons, we've implemented several key Creational, Structural, and Behavioral patterns, culminating with the Command pattern. You've built a solid toolbox of solutions to common software design problems. Now, we'll shift our focus from implementing individual patterns to the crucial skill of selecting and justifying the right one for a given scenario. This is a cornerstone of the "architectural mindset" you're aiming to cultivate—it's about making deliberate, defensible design decisions.
Our goal today is to select and justify an appropriate design pattern for a given design problem. We will develop a systematic approach for analyzing a problem, identifying candidate patterns, and articulating why one choice is more suitable than another.
1. A Framework for Choosing Patterns
With over 23 patterns in the original "Gang of Four" book, how do you even begin to choose? The first step is to identify the fundamental nature of the design problem you're trying to solve. Does your problem concern object creation, the composition of objects, or the interaction between them?
This leads to the three main categories we've discussed:
- Creational Patterns: Deal with object instantiation mechanisms, trying to create objects in a manner suitable to the situation.
- Structural Patterns: Deal with object composition, forming larger structures from individual parts.
- Behavioral Patterns: Deal with communication between objects, their responsibilities, and the algorithms they use.

To go from this high-level map to a concrete choice, we need a more detailed guide.
Why You Should Master Design Patterns
The following resource provides an excellent quick-reference guide to all 23 GoF patterns and a checklist for making a decision. This will form the basis of our selection framework.
Please read 'Section 4: How to Choose the Right Design Pattern'. Pay close attention to two parts: Tabular Comparison: Scan the table to refamiliarize yourself with the purpose and use case of the various patterns. You don't need to memorize it, but get a feel for the variety. Decision-Making Checklist: This is the key takeaway. Internalize these five steps as a structured thought process.
The checklist you just read provides a great strategic overview. To summarize, your process should be:
- Identify the Problem Type: Is it creational, structural, or behavioral?
- Assess Scalability & Complexity: Will the pattern support future growth without being overly complex for the current problem?
- Consider Team Expertise: Is this a pattern your team can easily understand and maintain?
- Prototype and Test: Does the pattern actually solve the problem elegantly in practice?
2. A Heuristic Approach: Asking the Right Questions
The framework above is a great start. Now, let's get more tactical. How do you map a problem description to a specific pattern? Often, it comes down to asking the right questions about what needs to be flexible or what needs to be encapsulated.
The following resource offers a brilliant, question-based guide to finding patterns.
Design Patterns: From Analysis to Implementation
This 'Design Pattern Matrix' from Net Objectives provides a set of diagnostic questions to help you pinpoint the right pattern.
Please review the section 'Guide to finding patterns in the problem domain' on page 11. Then, quickly scan the 'Indicators in analysis' and 'Indicators in design' for each pattern category on the preceding pages to see how these questions connect to specific patterns.
Let's distill those heuristics into a mental model:
- Are you trying to manage how objects are created?
- Do you need to create families of related objects? → Abstract Factory
- Do you need to construct a complex object step-by-step? → Builder
- Do you want subclasses to decide which class to instantiate? → Factory Method
- Do you need to ensure only one instance of an object exists? → Singleton
- Are you trying to manage the structure of your classes and objects?
- Do you need to make an existing class's interface match what a client expects? → Adapter
- Do you want to provide a single, simplified interface to a complex subsystem? → Facade
- Do you need to add new responsibilities to an object dynamically? → Decorator
- Do you want to treat a group of objects the same way as a single object? → Composite
- Are you trying to manage the behavior and communication between objects?
- Do you need a list of objects to be notified when a state changes? → Observer
- Do you need to select an algorithm at runtime? → Strategy
- Do you need to encapsulate a request as an object (e.g., for queues or undo)? → Command
- Do you want to reduce coupling between many objects by having them communicate through a central point? → Mediator
This question-driven approach helps you quickly narrow down the possibilities from 23 to a handful of likely candidates.
3. Justifying Your Choice by Comparing Alternatives
Selecting a pattern is half the battle. Justifying it is what demonstrates deep understanding. This often involves explaining why you chose pattern A instead of pattern B, especially when they seem similar.
For example, Adapter, Facade, Proxy, and Decorator are all "wrapper" patterns. How do you choose? It comes down to intent.
Let's return to the "Design Pattern Matrix" to see how to differentiate them.
Design Patterns: From Analysis to Implementation
This section of the document provides a direct comparison of commonly confused patterns, which is essential for justifying your design choices.
Please read the section 'Comparing patterns' on page 12. Focus on the two tables: 'Comparing Adapter, Façade, Proxy and Decorator' and 'Comparing Strategy and Bridge'.
Here's a summary of the key distinctions based on their intent:
| Pattern | Intent | Analogy |
|---|---|---|
| Adapter | To make an incompatible interface work with a client that expects another. | A travel plug adapter for different outlets. |
| Facade | To provide a simplified interface to a complex subsystem. | A single "Start Car" button for a complex engine. |
| Decorator | To add responsibilities to an object dynamically and transparently. | Adding toppings to a pizza. |
| Proxy | To control access to an object (e.g., for lazy loading, security). | A security badge to access a building. |
Being able to articulate these differences is a hallmark of a good software architect.
4. Case Study: Designing a Notification System
Let's apply this framework to a practical problem.
Scenario: You are designing a notification system for an e-commerce platform.
- The system must send a notification when an order is shipped.
- Initially, it only needs to send emails.
- In the future, it will need to support SMS and in-app push notifications.
- A user should be able to select their preferred notification method. Eventually, they might want to receive notifications through multiple channels (e.g., email and SMS).
- The core
OrderServiceshould not have to be modified every time a new notification channel is added.
Let's walk through the decision process:
-
Identify the Problem Type: The core of the problem is about handling different ways of doing something (sending a notification) and swapping them based on user preference. This is a classic Behavioral problem. We also need to create the right notification sender, which hints at a Creational pattern.
-
Ask the Right Questions:
- "Is there variation of a business rule or an implementation?" Yes, the notification logic varies. The heuristic guide points to the Strategy pattern. We can define an
INotifierinterface and haveEmailNotifier,SmsNotifier, etc., as concrete strategies. - "Do we need to add some function?" Yes, users might want to combine notification channels. The guide suggests Decorator to add multiple functions. We could have a
MultiChannelNotifierthat wraps severalINotifierinstances. - "Are we trying to make things?" Yes, we need to create the correct notifier object based on user settings. This suggests a Factory pattern (either Factory Method or Abstract Factory) to encapsulate the creation logic.
- "Is there variation of a business rule or an implementation?" Yes, the notification logic varies. The heuristic guide points to the Strategy pattern. We can define an
-
Synthesize and Justify:
A combination of patterns seems ideal here.- Strategy is the core. It allows the
OrderServiceto depend on theINotifierinterface, upholding the Open/Closed Principle. The service can execute a notification strategy without knowing its concrete type. - Factory is needed to instantiate the correct strategy. The
OrderServiceshouldn't contain aswitchstatement to decide which notifier to create. Instead, it can ask aNotifierFactoryto provide the correct object based on the user's preferences. - Decorator could be used to handle the requirement of multiple channels. A
SmsAndEmailNotifiercould be a decorator that holds both anSmsNotifierand anEmailNotifier.
- Strategy is the core. It allows the
This combination of Strategy and Factory is extremely common and powerful. Let's see it in action.
Strategy and Factory Pattern, The Best Software Design Pattern Combo
This video walks through the exact problem we just analyzed. It's a perfect demonstration of how to combine the Strategy and Factory patterns to build a flexible and extensible system.
Please watch the entire video (it's short!). Pay attention to how: Strategy decouples the OrderService from the notification implementations. Factory is introduced to create the correct strategy at runtime. Decorator is proposed as a solution for handling multiple channels. The video concludes by discussing the trade-offs of abstraction (complexity vs. flexibility).
Test your understanding!
You are building a data export module for a web application. The module should allow users to export data as either a CSV file or a PDF document. The core logic for fetching the data is the same in both cases, but the formatting and file generation steps are completely different. The process is as follows:
- Fetch data from the database.
- Format the data.
- Generate the file.
- Return the file to the user.
You want to structure this so that adding a new format (e.g., XML) in the future requires minimal changes to the existing structure. Which primary design pattern would you use to structure the overall algorithm, and why?
Show answer
The most appropriate pattern here is the Template Method pattern.
Justification:
The problem describes a fixed algorithm (fetch, format, generate, return) where some steps are invariant (fetch data) while others vary (format, generate file).
The Template Method pattern is designed for this exact scenario. You would create an abstract base class, DataExporter, with a export() method that defines the skeleton of the algorithm.
abstract class DataExporter {
// The template method
public final File export() {
Data data = fetchData();
FormattedData formatted = formatData(data);
File file = generateFile(formatted);
return file;
}
// Common step implemented in the base class
private Data fetchData() {
// ... logic to fetch data from DB
}
// Steps to be implemented by subclasses
protected abstract FormattedData formatData(Data data);
protected abstract File generateFile(FormattedData formatted);
}
Concrete subclasses like CsvExporter and PdfExporter would then implement the formatData and generateFile methods. This keeps the overall process consistent while allowing for different implementations of specific steps, fulfilling the requirement for easy extension.
Conclusion
You've now reached a significant milestone in your journey toward an architectural mindset. Choosing the right design pattern is not about memorization; it's a process of systematic analysis, questioning, and justification.
Key Takeaways:
- Analyze First: Start by identifying the core intent of your design problem: Is it about creation, structure, or behavior?
- Use Heuristics: Use question-based guides and pattern catalogs to narrow down your options to a few likely candidates.
- Compare and Justify: For similar patterns, focus on their specific intent to justify your choice. Articulating the "why" is as important as the "what."
- Combine Patterns: Real-world problems often require combining multiple patterns (like Strategy and Factory) to create a robust and flexible solution.
Preview of the Next Lesson:
This lesson concludes our module on code-level design patterns. We will now move up a level of abstraction to system-level architecture. In the next module, "Database Architecture & Design," our first lesson will be on the CAP Theorem. This fundamental principle governs the trade-offs in distributed database systems and will be your first step into designing for large-scale, resilient data storage.