The Cost of Purity
It started with a simple request: “Update the button tooltip text.” What should have been a one-line change instead required modifying 8 files: three services, two enums, a routing configuration, and two components. The culprit? A developer’s insistence on “pure pattern implementation.”
We've turned GoF patterns into religious texts, and now we're sacrificing working software at the altar of theoretical correctness. Let me show you the wreckage—the frameworks that succeed by breaking the rules, the teams that ship by ignoring the priests, and the dirty secret that production code is where patterns go to get practical or get deleted.
The Myth of Pure Implementation
Design patterns, as originally conceived, were never meant to be rigid templates. The Gang of Four described them as “descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.”
Note the key words: customized **and **particular context.
Somewhere along the way, patterns morphed from helpful guides into religious doctrine. The pattern purist emerged—someone who values theoretical correctness over practical utility, who would rather rebuild a system than violate an abstraction.
How Real Frameworks Break “Pure” Patterns
If pattern purity were essential, our most successful frameworks would be failing case studies. Instead, they thrive by adapting patterns to real constraints.
Angular Dependency Injection: The Pragmatic Hybrid
Angular’s DI system demonstrates this balance beautifully. It offers constructor injection (pattern pure) alongside intentional “impurities”:
// Service locator pattern – textbook anti-pattern
constructor(private injector: Injector) {
const service = this.injector.get(MyService);
}
// Hard-coded dependency – the exact opposite of DI's goal
providers: [
{ provide: API_ENDPOINT, useValue: 'https://api.example.com' }
]
Why would the Angular team include such blasphemies? Because real software needs escape hatches. Legal compliance, third-party integrations, and testing demands create scenarios where abstraction only adds friction. useValue exists because sometimes you just need to wire A to B and move on.
Singleton? Not So Much
The Singleton pattern is a purist favorite. But look at how real frameworks adapt it:
• React Context: Each Provider creates its own instance scope that descendants inherit—hierarchical, not global. This is the Provider Pattern, taking the "single shared instance" concept but making it composable and overrideable.
• Vue's Provide/Inject: Creates a parent-to-child dependency chain where values can be provided at any level and overridden downstream. Again, hierarchical and context-aware rather than truly singleton.
• Angular Services with providedIn: 'root': Creates a true app-wide singleton... unless you provide it again in a component or module, which creates a new instance for that scope. Even Angular's "singletons" aren't always singular.
• iOS/Swift Modern Practice: While Apple's frameworks use classic singletons (FileManager.default), the community increasingly prefers dependency injection through initializers. DI frameworks like Swinject provide singleton scoping without the global static access that makes testing painful.
• Android with Dagger/Hilt: @singleton annotations create container-managed singletons—shared instances without the static instance variables and private constructors of the textbook pattern.
None of these implement the classic GoF Singleton pattern. They take the core idea—"shared instance"—and adapt it to their frameworks' needs: testability, modularity, tree-shaking, or hierarchical composition.
Observer Pattern: From Textbook to RxJS
Compare the Observer pattern in the GoF book to RxJS. While RxJS includes observer-like mechanics, it's fundamentally a different paradigm: reactive programming. RxJS added:
• Operators and pipes for functional data transformation (not in the pattern)
• Hot vs. cold observables for stream lifecycle management (pattern adaptation)
• Multicasting and scheduling for real-world performance optimization
If RxJS had implemented only the 'pure' GoF Observer pattern—simple notification callbacks—we wouldn't have the powerful reactive ecosystem we rely on today. The framework adapted the core idea into something far more useful.
The Hidden Costs of Pattern Zealotry
The tooltip debacle revealed a painful irony: the pursuit of pattern purity often achieves the opposite of its intended goals.
The purist wanted:
• Decoupled, maintainable code
• Testable components
• Clean separation of concerns
What we got:
• A cage of abstractions we're now trapped in
• Tests so fragile they mock their own existence
• Architecture so 'clean' it needs committee approval for a text change
This isn’t theoretical. It’s the daily reality in teams where pattern dogma overrides pragmatism.
The Five Realities Purists Ignore
1. Business Logic Isn’t Academic
Legal requirements, compliance rules, and business constraints create unique, one-off needs. GDPR logging for EU users? A/B test tracking for experiment #247? These aren’t abstractable problems—they’re specific needs demanding specific solutions.
2. Time Is a Finite Resource
Businesses pay for working software, not perfect architecture. The daily choice isn’t “perfect pattern vs. bad code” — it’s “ship today vs. ship never.” Over-engineering is just as destructive as under-engineering.
3. Your Team Isn't Your Audience
That junior trying to fix a bug at 2 AM? They don't care about your beautiful pattern cathedral. The senior reviewing your code from another squad? They're not impressed by your pattern acrobatics. Good code explains itself; clever code just makes you feel smart while everyone else suffers."**
4. Performance Isn’t Abstract
Every abstraction layer has cost: bundle size, execution time, memory overhead. Virtual DOM wasn’t in any pattern book, but it made web apps usable on real devices. Pragmatism beats purity when users are waiting.
5. Requirements Evolve
Today’s perfect abstraction becomes tomorrow’s constraint. Systems that can’t adapt to new requirements—because they’re too coupled to their own patterns—fail.
A Better Mindset: Patterns as Lenses, Not Laws
Instead of asking “How purely can we implement this pattern?”, try:
1. What’s the actual problem?
Not “How can we use the Strategy pattern?” but “We need to sort this list differently in three contexts.”
2. What’s the simplest thing that works?
Sometimes a configuration object beats a factory. Sometimes an if statement beats a strategy. Simplicity is its own virtue.
3. What will actually change?
Base decisions on your roadmap and user feedback, not hypothetical future needs. YAGNI isn’t laziness—it’s focus.
4. Can our team maintain this?
The best abstraction is the one your team understands without a week of onboarding.
The Maintenance Test
The true test of code quality isn’t pattern purity—it’s maintainability. Imagine a developer joins your team six months from now. They need to:
In the purist codebase: Understand the pattern landscape, trace inheritance hierarchies, and learn the “right” way to extend the system.
In the pragmatic codebase: Find the relevant code, make the change, verify it works.
Which codebase is actually better engineered?
Conclusion: From Religion to Engineering
Patterns are invaluable—as inspiration, as shared vocabulary, as starting points for solutions. But the moment they become commandments, they start destroying the very things they promise to protect: working, maintainable, adaptable software.
Look at the frameworks we actually use. They're not pure—they're practical.They give us escape hatches, pragmatic shortcuts, and context-aware solutions because they’re built by engineers who’ve faced real constraints.
So next time someone starts preaching pattern purity, hit them with reality:
What actual user cares about this purity? (Spoiler: none of them)
How many files do we touch to change a button label? (Hint: if it's more than one, you've failed)
Show me one production system at scale that implements this 'purely' (They can't—because they don't exist)
What's the actual cost? Not in theory, but in missed deadlines, frustrated teams, and features that never ship
Remember this: users experience your software, not your architecture. They care if the button works, not how beautiful your seven-layer injection chain is.
The engineers I respect aren't pattern priests. They're pragmatic problem-solvers who know when to use a pattern, when to adapt it, and—most importantly—when to tell it 'not this time, baby' and just write the damn code.
That's not 'compromising on quality.' That's the difference between academic exercises and actual engineering. And I know which one I'd rather have in production.
Top comments (0)