Creation date: 2009-01-12T01:09:02
Design patterns were born in books, but we live in code. Today I want to talk about what they actually buy us when we ship web apps, desktop tools, or that shiny mobile thing you are putting together for the App Store or the first Android phones. This is part 18 of my ongoing notes from the trenches, and it is about practical uses and traps that keep showing up in client projects and side gigs.
Dialogue style intro
You: Patterns feel like homework. Do we really need them?
Me: Only when they solve your mess. When a pattern makes your tests simple or keeps a new feature from breaking five other parts, it earns its keep.
You: So which one should I use first?
Me: Wrong question. Start with the problem. If you have lots of if statements that choose behavior, reach for Strategy. If a class keeps talking to a slow thing and your UI freezes, reach for Command with a queue. If everything depends on a global object, throw it out and let a container pass what is needed.
You: Sounds fancy. Any proof that these ideas work in the wild?
Me: Keep reading. We have plenty of real signs. From Java IO streams to .NET events to Rails callbacks, the patterns are already under your feet.
Evidence section
Patterns are not just lecture slides. You use them every day, maybe without calling them by name. A few hard facts you can check in your editor right now:
Decorator keeps Java IO alive. Open any Java code that stacks a BufferedInputStream on top of a FileInputStream and you will see a pure Decorator stack. It adds behavior without subclass soup. Every time you add buffering, compression, or counting on top of a stream, you are doing it.
Observer drives UI events. Swing listeners, .NET event handlers, and browser addEventListener in JavaScript are all Observer. One subject, many listeners, no tight coupling. This is why your button click does not know or care about five different places that react to it.
Factory spares you from new soup. Logging in Java through LoggerFactory, database drivers through DriverManager, and even Spring bean creation all use a Factory to centralize creation. Same story in PHP with PDO drivers and in Ruby with Rack middleware builders.
Strategy decides behavior at runtime. In Java you pass a Comparator to sort in different ways. In .NET LINQ with OrderBy uses a selector. In Ruby you pass a block. The pattern is the same idea every time. Behavior is selected and swapped without if storms.
Template Method shapes frameworks. Rails callbacks before_save and after_commit, JUnit setup and teardown, and Servlet life cycles in Java all standardize a flow and let you slot in your part. That is straight Template Method.
Adapter keeps old and new talking. JDBC drivers wrap everything under a common interface. In .NET, you adapt old COM calls so new code can live with them. In JavaScript, many libraries shim browser quirks to present one clean API. One face for many mismatched parts, that is Adapter.
Command brings undo and queues. Desktop apps store actions to undo and redo without digging into UI guts. On the server side you put work on a queue for delayed jobs in Rails or MSMQ in .NET and you get retries and load smoothing. Each task becomes an object that can be saved, retried, or logged.
Builder reads like English. StringBuilder in .NET and Java avoids garbage storms. SQL builders in many ORMs such as Hibernate and ActiveRecord help you compose queries step by step. The idea is to keep construction readable when a constructor gets unwieldy.
Look around and you will see patterns everywhere. Mobile SDKs are pushing MVC hard. The browser war is heating up with Chrome on the scene and Firefox feeling fast. Git is all the buzz, though most shops stay on Subversion. In the middle of all this, the patterns give names to shapes we already trust. Naming a shape helps your team move faster in code reviews and during those whiteboard sprints.
Notes from the field
I keep a pocket list of rules I use when a project smells like it needs a pattern. Not theory. Just what survives deadlines.
- Reach for Strategy when if chains grow. Once you hit three or four branches that all differ by one behavior, craft a small interface and split the cases into tiny classes. In Java, C sharp, Ruby, or PHP, it pays for itself after the third change request.
- Let a container wire your stuff. Spring, Guice, StructureMap, Unity, Pico, pick your poison. Passing collaborators through constructors beats hidden globals. Tests become simple. Your main method sets up the graph and the rest of the code stays clean.
- Wrap slow or flaky things with Command. If a remote call or file write can fail, wrap it as a command and put retry logic on the outside. This moves error handling away from the happy path and gives you metrics and logging for free.
- Use Adapter to protect your core. External SDKs change. Version bumps hurt. Place an adapter at the edges so the churn stays outside. Your core code talks to your own interface only.
- Favor composition over inheritance. Decorator and Strategy give you small parts you can snap together. Big inheritance trees lock you in and make testing painful.
- Keep names boring and clear. PriceCalculator, ImageResizer, Clock. Simple names beat clever ones. When you read the file list you should know what each does.
- Start with the simplest thing. One case, one method, no pattern name. When a second or third case arrives, extract the pattern. Refactoring is your friend.
- Make seams for tests. Inject a Clock or a Random so tests can control time and chance. This single move saves hours of flakiness across languages.
- Document the trade. When you add a pattern, add a short comment or a README section that says what problem it solves. Future you will say thanks.
For web folks on Rails or Django, you already live with Template Method, Observer, and Builder in your daily code. For Java or C sharp teams, a small container and a clear package layout reduce the feeling that everything knows about everything. For PHP teams, moving toward classes with clear roles and avoiding hidden globals goes a long way. For JavaScript which is finally gaining strong libraries, Strategy patterns with simple functions beat nested ifs and keep event code readable.
Performance notes matter right now because we all ship to browsers that still choke on heavy pages and to phones with modest memory. Patterns have overhead. Do not panic, just be mindful. Many small objects are fine on the server with the current JVM or CLR, but on mobile you want fewer allocations. Use patterns where they pay off in clarity, then profile the hot spots.
Risks and traps
Patterns help, but they can also set a trap. Here are the ones I see the most along with quick exits.
- Singleton turns into a global sink. It looks easy until tests fail because state leaks across runs. In servers it creates shared state that is hard to reason about. Favor passing a shared service into the places that need it. If you must share, keep it stateless and thread safe. Better yet, let the container manage a single instance.
- Too many patterns at once. Teams read a book and rebuild the shop floor. The result is layers for the sake of layers. Resist that urge. Apply one pattern to one pain point. Stop. Measure the result in lines deleted and test clarity.
- N plus one queries from lazy loading. ORMs make it easy to forget what runs against the database. Watch your logs. If one page does two hundred selects, teach your ORM to fetch what you need in one go. Patterns will not save you from bad queries.
- Observer leaks listeners. UIs and services attach like Christmas lights and never let go. Cue memory leaks. Unsubscribe where you subscribe. In .NET be careful with event handlers and in Java remove listeners when you dispose components.
- Decorator that never stops decorating. A stream wrapped in another stream wrapped in another stream can hide the real cause of a failure. Keep the chain short and add logging around boundaries.
- Factories that hide rules. When a factory becomes a black box, junior devs stop knowing how objects are composed. Keep factories thin. When there are many rules, put them in small classes with names and tests.
- Threading with Command and queues. Queues look simple until retries and ordering bite you. Decide what happens on duplicate work. Log with identifiers. Make commands idempotent so a retry does the same thing twice without double charging a card.
- Adapter that grows into a new SDK. An adapter should mask a difference, not replace the world. Keep it focused. When it starts to mirror the full external API, step back and cut scope.
On the culture side, watch for pattern speak that confuses rather than helps. Saying we need a Chain of Responsibility when you mean we need a small pipeline for requests is not helping. Patterns are a shared shorthand. Use the names only when they shorten the chat.
Graceful exit
Here is the short playbook I keep on my desk.
- Start with the smell, not the pattern. Long if chain, god object, slow UI, flaky external call. Name the pain. Then pick the smallest pattern that removes it.
- Keep the shape visible. A folder that holds strategies. A folder that holds commands. README that says what talks to what. Make the structure obvious.
- Refactor toward patterns, not away from clarity. If the pattern adds more files and no relief, back it out. Your version control has your back.
- Teach by pairing. When a teammate hits a snag, pair and refactor together into the pattern with tests. Story driven learning beats a slide deck.
- Measure wins. Count the if statements you removed. Time how long a new feature takes now compared to last month. Share the results in a short note so the team sees why it matters.
We are shipping into a world where Chrome just landed, Android is new, the App Store is full of gold rush dreams, and people still have to support IE6 at the office. The stack is noisy and budgets are tight. Patterns are not magic, but they are a solid set of moves you can repeat when the same old traps appear. Use them to tame your codebase so you can spend more time on what users touch and less on wrestling with tangled classes.
Keep a small note near your monitor with these prompts. Strategy for behavior swaps. Command for recoverable work. Adapter at the edges. Decorator for layering features. Factory to centralize creation. Observer for events. Builder when construction gets messy. Template Method for shared flow. When the smell shows up, you will already know where to reach.
Then close the book and get back to code. Patterns pay off when they vanish into the background and your app just feels right. That is the goal. Clean moves. Fewer surprises. A team that speaks the same language without long meetings. See you in the next one.