Loading learning content...
You've identified your problem. You've categorized it into a problem family. You have 3-5 candidate patterns. Now comes the critical question: Which pattern actually fits?
Pattern evaluation is where good engineers distinguish themselves from pattern enthusiasts. A pattern enthusiast picks the most elegant pattern. A good engineer picks the pattern that solves the problem with acceptable tradeoffs. Sometimes that's the elegant solution; often it's the boring one.
The Uncomfortable Truth About Pattern Fit
No pattern is a perfect fit. Every pattern comes with costs—additional abstraction, indirection, complexity, or constraints on future evolution. Pattern evaluation isn't about finding a perfect match; it's about finding the pattern where the benefits significantly outweigh the costs for your specific context.
By the end of this page, you will know how to evaluate candidate patterns against your problem using structured criteria. You'll learn consequence analysis, context verification, and how to compare multiple patterns systematically. You'll also understand when the best choice is no pattern at all.
Each design pattern is defined by more than its structure. A pattern's full definition includes:
Fit evaluation examines each dimension:
When a pattern passes all four dimensions, you have a strong candidate. When any dimension fails, you either need a different pattern or need to modify your problem understanding. Forcing a pattern despite fit failures is a recipe for regret.
Every pattern has a stated intent—a concise description of what problem it addresses. Intent match is your first evaluation gate.
Canonical Pattern Intents (from the Gang of Four and common sources):
| Pattern | Stated Intent |
|---|---|
| Strategy | Define a family of algorithms, encapsulate each, make them interchangeable. Lets algorithm vary independently from clients using it. |
| Observer | Define a one-to-many dependency between objects so that when one changes state, all dependents are notified and updated automatically. |
| Factory Method | Define an interface for creating an object, but let subclasses decide which class to instantiate. |
| Abstract Factory | Provide an interface for creating families of related or dependent objects without specifying their concrete classes. |
| Decorator | Attach additional responsibilities to an object dynamically. Provides a flexible alternative to subclassing for extending functionality. |
| Adapter | Convert the interface of a class into another interface clients expect. Lets classes work together that couldn't otherwise. |
| State | Allow an object to alter its behavior when its internal state changes. The object appears to change its class. |
| Command | Encapsulate a request as an object, thereby letting you parameterize clients, queue or log requests, and support undoable operations. |
How to Evaluate Intent Match
Compare your problem statement directly against the pattern intent:
Example Problem Statement:
"Our notification system has four delivery channels (email, SMS, push, Slack). Each channel has different APIs and logic. We need to add new channels without modifying existing notification logic. The core notification process (preferences check, logging, rate limiting) must remain consistent."
Comparing Against Strategy Intent:
"Define a family of algorithms, encapsulate each, make them interchangeable."
Intent Match Score: Strong
Comparing Against Decorator Intent:
"Attach additional responsibilities to an object dynamically."
Intent Match Score: Weak
Strategy passes the intent match filter; Decorator doesn't. This doesn't mean Decorator is bad—it means Decorator isn't designed for this problem.
Intent match gets you past the first filter, but doesn't guarantee fit. A pattern can have matching intent but fail on applicability, participants, or consequences. Complete all evaluation dimensions.
Pattern documentation typically includes explicit applicability conditions—contexts where the pattern is appropriate. Treat these as a checklist.
Example: Strategy Pattern Applicability (from GoF)
Use Strategy when:
Verification for Notification System:
| Applicability Condition | Our Context | Match? |
|---|---|---|
| Many related classes differ only in behavior | Email, SMS, Push, Slack channels differ only in how they send—same conceptual operation | ✅ Yes |
| Need different variants of an algorithm | Need different sending implementations per channel | ✅ Yes |
| Algorithm uses client-hidden data | Each channel has API keys, auth tokens clients shouldn't access | ✅ Yes |
| Class defines many conditional behaviors | Would otherwise have switch/if-else on channel type | ✅ Yes |
When Conditions Partially Match
Rarely will all conditions apply perfectly. Evaluate whether:
Red Flags in Applicability Verification:
Blog posts and tutorials often simplify pattern applicability. When evaluating fit, consult authoritative sources (Gang of Four, Pattern-Oriented Software Architecture books) for complete applicability criteria.
Every pattern defines participants—the roles that collaborate to form the pattern. To apply a pattern, you must map your domain elements to these roles clearly and naturally.
Strategy Pattern Participants:
Mapping to Notification System:
| Pattern Participant | Our Domain Element | Responsibilities |
|---|---|---|
| Strategy | NotificationChannel interface | Declares send(notification, recipient) operation |
| ConcreteStrategy | EmailChannel, SMSChannel, PushChannel, SlackChannel | Each implements sending via its specific API |
| Context | NotificationService | Receives notification request, selects appropriate channel, delegates sending |
Quality Checks for Participant Mapping
Natural Fit: Do domain elements map to pattern roles without conceptual gymnastics? A forced mapping indicates misfit.
Complete Coverage: Can you identify all pattern participants in your domain? Missing participants might mean you need pattern pieces you don't have.
Responsibility Alignment: Do your domain elements' responsibilities match what the pattern expects of each participant? Responsibility mismatch causes implementation friction.
Cardinality Match: Does your domain have the right number of elements? Strategy expects one Context and multiple ConcreteStrategies—do you have that structure?
Example of Poor Participant Mapping
Suppose we tried to map Decorator to our notification system:
NotificationChannel?This mapping breaks down immediately. Decorator expects nested wrapping (each decorator wraps another component). Our channels don't wrap each other—they're alternatives, not layers. Participant mapping fails, signaling poor fit.
When mapping participants, literally draw a diagram with pattern roles on one side and your domain elements on the other. Lines connecting them should be clear and non-overlapping. If lines cross or loop, your mapping is confused.
Every pattern comes with consequences—explicit tradeoffs that you inherit by adopting the pattern. Consequence analysis is the most important and most overlooked step in pattern evaluation.
Strategy Pattern Consequences (from GoF):
Benefits:
Costs:
Evaluating Consequence Acceptability
| Consequence | Evaluation | Acceptable? |
|---|---|---|
| Clients must know strategies exist | Our notification service selects channel based on preference; clients don't know channels—acceptable because selection is encapsulated | ✅ Yes |
| Communication overhead | Notification context passes message data to channel; overhead is minimal compared to network send time | ✅ Yes |
| Increased object count | 4 strategy objects plus one per new channel; negligible memory impact | ✅ Yes |
| Families of algorithms (benefit) | Exactly what we want—all channels are a coherent family | ✅ Benefit applies |
| Eliminates conditionals (benefit) | Replaces switch-on-channel-type with polymorphism | ✅ Benefit applies |
Context-Dependent Consequence Evaluation
Consequences that are acceptable in one context may be dealbreakers in another:
Example: Increased Object Count Consequence
Example: Client Awareness Consequence
Unacceptable Consequences = Pattern Rejection
If any consequence is genuinely unacceptable given your constraints, the pattern doesn't fit—regardless of how well intent, applicability, and participants matched. You need a different solution.
The temptation is to convince yourself that a consequence 'isn't that bad' because you want the pattern. Be intellectually honest. If a consequence conflicts with hard requirements, acknowledge it and explore alternatives.
When multiple patterns pass individual evaluation, you need systematic comparison. This prevents defaulting to the pattern you know best rather than the pattern that fits best.
Comparison Framework: Weighted Criteria Matrix
Create a matrix with evaluation dimensions, weight each dimension by importance to your context, and score each candidate pattern.
| Criterion | Weight | Strategy Score | State Score | Template Method Score |
|---|---|---|---|---|
| Intent match clarity | 25% | 9/10 | 6/10 | 5/10 |
| Applicability condition coverage | 20% | 10/10 | 5/10 | 7/10 |
| Participant mapping naturalness | 20% | 9/10 | 4/10 | 6/10 |
| Consequence acceptability | 20% | 8/10 | 7/10 | 8/10 |
| Implementation complexity | 10% | 8/10 | 6/10 | 9/10 |
| Team familiarity | 5% | 7/10 | 7/10 | 9/10 |
| Weighted Total | 100% | 8.65 | 5.55 | 6.65 |
Adjusting Weights
Weight selection matters. Common weight considerations:
Tie-Breaking Principles
When scores are close:
When No Pattern Wins Clearly
If weighted scores are truly equivalent, consider:
The comparison matrix becomes part of your design documentation. When future maintainers ask 'Why Strategy instead of State?', the matrix shows the reasoning process, not just the conclusion.
Sometimes, honest evaluation reveals that no pattern adequately addresses your problem. This is a valid—and often correct—conclusion. Forcing a pattern when none fits creates more problems than it solves.
Signs That No Pattern Fits
Alternatives When Patterns Don't Fit
Simple Procedural Solution
Custom Hybrid Design
Decompose the Problem
Architectural Patterns
Accept Technical Debt
Deciding not to use a pattern should be documented and justified just like deciding to use one. 'We evaluated Strategy, State, and Template Method. None fit because [specific reasons]. We're using [alternative approach] instead.'
Let's walk through a complete pattern evaluation for a realistic problem.
Problem Statement
Our e-commerce order processing has grown complex. An order can be in states: Draft, Submitted, PaymentPending, PaymentFailed, PaymentComplete, Shipped, Delivered, Cancelled. Different operations are valid in different states. Currently, every operation method has a switch statement checking current state. Adding new states or changing state-specific logic requires modifying multiple switch statements across many methods.
Problem Analysis Recap (from previous page):
Evaluating State Pattern
| Dimension | Analysis | Score |
|---|---|---|
| Intent Match | "Allow object to alter behavior when state changes" — exactly our problem | 10/10 |
| Applicability | Behavior depends on state (✅), many state-specific conditionals (✅), state transitions at runtime (✅) | 9/10 |
| Participants | Context=Order, State=OrderState interface, ConcreteState=DraftState, ShippedState, etc. | 9/10 |
| Consequences | More classes (8 state classes) — acceptable for clarity. State transition logic localized — desired. | 8/10 |
Evaluating Strategy Pattern
| Dimension | Analysis | Score |
|---|---|---|
| Intent Match | "Interchangeable algorithms" — our concern is state-dependent behavior, not algorithm variation | 5/10 |
| Applicability | Related classes differing in behavior — partially. But state changes, strategies are typically fixed | 4/10 |
| Participants | Context=Order, Strategy=???. Each state as a strategy? Awkward—strategies don't know about transitions | 4/10 |
| Consequences | Clients must select strategy — doesn't match; states transition automatically based on events | 4/10 |
Comparison Result:
Conclusion: State pattern fits strongly. Strategy pattern doesn't fit—not because it's bad, but because our problem is state-transition behavior, not algorithm substitution.
Implementation Decision:
Proceed with State pattern. Create:
OrderState interface with operations: submit(), processPayment(), ship(), deliver(), cancel()Order (Context) holds current state, delegates operations to state, updates state based on transition methodsThis analysis, document, and decision took perhaps an hour. It will save weeks of debugging scattered switch statements as order complexity grows.
By systematically evaluating both candidates, we made an informed choice with documented reasoning. When a teammate asks 'Why not Strategy?', we have a clear answer: Strategy is for algorithm variation; State is for state-dependent behavior. Our problem is the latter.
Pattern evaluation transforms pattern selection from intuition to engineering. Use structured evaluation to ensure pattern fit.
What's Next:
With your pattern candidates evaluated, the next page covers Considering Alternatives—exploring solutions beyond the standard pattern catalog, including pattern variants, combinations, and non-pattern solutions that might better serve your needs.
You now have a rigorous framework for evaluating pattern fit. Systematic evaluation prevents both pattern misapplication and analysis paralysis. By checking intent, applicability, participants, and consequences, you ensure chosen patterns solve actual problems with acceptable tradeoffs. Next, we examine alternatives beyond catalog patterns.