Skip to main content

Command Palette

Search for a command to run...

How “Design Patterns Explained” Changed the Way I Design Software

Updated
6 min read
How “Design Patterns Explained” Changed the Way I Design Software
N

I build apps, internal tools, and sketch stuff on my iPad. Into side projects, and occasionally sharing thoughts here.

I read Design Patterns Explained 2nd Edition a few months ago. I thought I'd finally write about it and some of the lessons I took away from it.
I was reading the final chapters while starting Offline Dashcam, a project at Wheelseye, a logistics company. Looking back, it was the perfect project to apply many of the ideas from the book.
This article is about how the principles in the book helped me design and code a complex feature.

A Shift in Thinking

There are books that give information or are used as references. But sometimes you read a book that changes the way you think.

Design Patterns Explained is one of those books for me. The book is for anyone who is looking to understand design patterns, not just catalog them, but actually understand why they exist, what problems they solve, how to recognise, implement, and refactor them.

It explained encapsulation through tons of examples, along with concepts like abstraction, polymorphism, and inheritance. More importantly, it showed how these ideas influence real design decisions: which classes should be abstracted and how classes should relate to and aggregate one another.

As engineers, we know features evolve. A “simple” requirement today can grow into something much bigger tomorrow.

I always tried to be ready for that. But I sometimes focused on the wrong kind of change. Things worked, they shipped — but updating them wasn’t always smooth. I knew I could improve how I approached design, but I couldn’t clearly explain what was missing.

I didn’t read Design Patterns Explained to solve this. I picked it up just to understand design patterns better.
But while reading, I started noticing the missing pieces in my own decisions.
And those realisations changed how I design software today.

This book didn’t teach me patterns. It changed how I think. And once that happened, patterns showed up naturally in my code.

Design Is Mostly About Change

One idea hit me early in the book:

Build for today. But keep the door open for tomorrow.

My usual flow was:

  1. Understand the feature

  2. Code it

  3. Adjust later

It wasn’t wrong. It just wasn’t always ready for the right changes.

The Offline Dashcam is a device with a built-in wireless hotspot and an SD card slot. It is attached to a vehicle and records video to the SD card. We can connect to its Wi-Fi to view, download, and delete those recordings. I was working on adding support for this feature in our mobile application ( built using Flutter 💙 ).

Offline Dashcam device with phone connected

Each vendor has different firmware with different chipsets. This means:

  • Different API endpoints

  • Different request bodies & responses

  • Different capabilities

We need to keep our codebase open to adding new chipsets, while maintaining the capabilities of each one without disrupting user experience.

Principles Before Patterns

Encapsulate what varies.

Before reading the book, I would often think in terms of features. The book pushed me to think in terms of change. Instead of asking "What does this feature do?", I started asking "What part of this feature is likely to change independently?"

I created a list of things that are varying. E.g.

  • The chipset initialisation process is different for each chipset.

  • The API endpoints and response protocols are different.

  • Capabilities of each chipset are different. (E.g. audio support)

This confirmed that I should have different repository and API layer for each chipset. The rest of the layers should not be aware of these differences. The initialisation process also varied by chipset, which naturally led me toward a factory-based approach.
I pondered various scenarios, tried the varying principle that I learned on all of them, and chose the design that best matched the kinds of change I expected.

Before, I might have handled this like:

if (chipset == A) { call ApiA.startRecording() }
else if (chipset == B) { call ApiB.startRecording() }

One bug in DeviceA logic could ripple into DeviceB's flow because they lived in the same messy function. Every new feature meant touching five files full of if (chipset == ...) blocks.

Now:

final device = DashcamFactory.create(chipsetType);
device.startRecording();

How This Changed My Architecture Decisions

Chipset differences do not spread through the codebase.
They live in one place.

+-----------------------+
| DashcamDevice (abstract)
|  startRecording()
|  stopRecording()
+-----------+-----------+
            |
     +------+------+
     |             |
+----v----+   +----v----+
| DeviceA|   | DeviceB |
| Chipset|   | Chipset |
+--------+   +---------+

At runtime, the correct implementation is selected.
The rest of the system doesn’t need to know which chipset it is talking to.

A Practical Insight I Took Away

When I stopped thinking “which pattern should I use?”
and instead started asking:

What here is stable, and what is likely to change?

Now I look for variability by asking questions. I interrogate new code: Does this depend on specific hardware? Will another vendor break this? Can the UI team change a colour without me refactoring the API layer? If the answer is yes, that piece gets its own box. Its own boundary. Its own file where it can change without asking permission.

Keeping Things Cohesive

Earlier, I sometimes built giant “god” BLoCs or Services that handled everything.
But now, I have use cases, which delegate to services, factories, repositories, etc., as needed.

Now:

  • UI layer handles UI stuff

  • Storage handles storage stuff

  • Hardware/API code stays near hardware/API code

No giant boss-component running the whole show.
If a new engineer joins later, they won’t have to decode one huge file to make a small change.

High cohesion → less stress.

A Real Change in Everyday Workflow

Earlier, if a new requirement came in — for example, “Also support taking snapshots while recording” — I would hesitate a bit.

Where should that logic go? Will it break recording flow? Will I end up refactoring too much?

Now, the path is clear:

  • A new feature → a new use case

  • Hardware behaviour → a new chipset implementation

  • API change → stays near the API service

  • UI behaviour → stays in its own layer

Refactoring and testing became more predictable because each piece has its own space.
When code has the right boundaries, future change stops feeling risky.

What Changed in Me (and What’s Next)

I didn’t expect this book to influence my day-to-day work.
But I caught myself designing with more clarity from the very next feature.

Now, I:

  • notice variability early

  • keep change local

  • choose simplicity by default

  • and think about the developer who joins after me

I’m still learning to balance all this, but my software is starting to feel more ready for whatever comes next.
If you’ve ever felt your code works but it doesn’t feel quite ready for the future, this book might help you spot the same gaps I did.

I picked up the book hoping to learn design patterns. I finished it thinking differently about change. The patterns were useful, but the mindset shift was what stayed with me.

B

"What is stable, and what is likely to change?" is probably one of the most useful architecture questions there is. We've been seeing something similar in BXRuntime recently. Several systems evolved independently, but once we started isolating what changes over time, a very different architecture emerged than the one we originally planned.

Great read.