My Favorite Design Patterns
May 27, 2024
Before I share my favorite design patterns, I want to share my favorite books that have inspired me to write this post:
- Game Programming Patterns by Robert Nystrom.
- Design Patterns by Erich Gamma, John Vlissides, Richard Helm, and Ralph Johnson.
- The Pragmatic Programmer by David Thomas and Andy Hunt.
These authors have completely reshaped how I approach problem solving. I no longer have a one-size-fits-all solution for everything. As a result, I now approach new projects with this inclusive question: “What are the most understandable solutions?”
As a long-time Stack Overflow user, I unfortunately inherited a dogmatic mentality that assumes the most voted answer is the best answer for my problem. These books unwind this narrow way of thinking by offering an abundant array of solutions that have been used for over 30 years.
Note: I often treat all projects like games, so I will share my favorite design patterns with that context (in no particular order).
Factory Method
If you are making a game, you will likely need a factory method to crank out a bunch of entity types (ex: player, goblins, trees, etc.). The factory method allows you to create new instances of objects without needing to pass a bunch of parameters with unique attributes.
Composite
After years of abusing the flyweight pattern, I am finally warming up to the power of composition. I find it much easier to say “The player has a health bar” rather than “The player is an entity, which is a 3D object, that has a health bar”. Composition allows me to focus on what an object has rather than what an object is.
Mediator
When I first started developing games, I often jammed all game functionality into a single player class. As a result, game logic was tightly coupled with the player instance, which made it difficult to communicate with other game objects (like enemies). To reduce direct communication between game objects, the mediator pattern is responsible for communicating behaviors between all objects (just like a street light).
Observer
If you have ever programmed a button that does something when clicked, then you have officially used the observer pattern. For example:
document.getElementById('my-button').addEventListener('click', function(e) { alert('do something'); });
As you can see, the button element will now notify the user when the button is clicked. With the observer pattern, we can loosely observe the button element and run a function based on the event type “click”. ThreeJS uses this same logic for all 3D objects, so it makes it incredibly simple to listen to object state changes without needing a mediator system.
Object Pool
This pattern pairs great with factory methods that need to construct instances with cached assets (like 3D models, textures, and audio). Having a pooled system of resources allows us to access data more quickly than “lazy loading” important information.