There have been several questions about design patterns over on gamedev.net recently. It must be that time of the school year again. I’ve seen some comments on the topic of design patterns and I’ve got some thoughts and notes that may be valuable in those cases. (If I’ve directed you here from the forum, it’s because I don’t want to retype all of this, so read it and figure out why it applies in your case.)
Design patterns are a concept that became popular in the late 1990s, from a book with that name. Patterns aren’t a specific implementation or a specific way of doing things. Instead they’re a name that refers to a general way of doing things that can have many variations. Imagine a pattern like chocolate chip cookie where there are many ways to create them, but if you know the name you generally know what you’re going to get. For software these are patterns like factory methods, visitors, template methods, and observers. Going through each, you might not know the details of how a factory method works, but you know it builds things; if someone uses a visitor you know a blob of data is being passed through the system; when template methods are used something is getting copied to build something new, and observers can be attached to respond when something changes. Over the decades thousands of patterns have been identified, documented and studied.
Don’t get upset about not knowing all the names. Having these names make it easier to people to reason about the software. Without names people need to resort to descriptions. For example, we could assign a name to something like “quicksort“, or we can describe it in detail. (This is a partition-based sort where ideally you subdivide the problem in half each time, but it can fail badly when the list is already sorted and it picks a terrible pivot, and…) After the Design Patterns book came out people started identifying and naming software patterns en masse. Many duplicate names were used, cliques would use their own names, but over the past 25 years many common names have become established. Even though I’ve been doing this for decades, sometimes at work someone will name a pattern they’re using and it will be unfamiliar. A few seconds talking about it or searching the web and I’ll usually recognize the pattern as a different name, or else learn something new.
Okay, on to what I’ve been seeing lately.
One common trend I’ve seen in some students is that they want to follow a specific example. Maybe it is the example given in their textbook or a website. Usually those are clear examples. I’ve done that myself many times both as a teacher and a student, it is far easier to follow a clear description that works out perfectly. When learning math we prefer simple round numbers and operations that work out exactly to teach the concept. Unfortunately real life means things don’t always work out perfectly. That’s okay. Getting back to the chocolate chip cookie, one recipe may call for oatmeal, another calls for peanuts, another wants walnuts. They may not be exactly like the ones you are familiar with, but after you’ve seen a few varieties you’ll recognize them when you see them.
Patterns must be customized. A common concern in the patterns is that the pattern doesn’t fit the problem exactly. This is a more common concern among beginners and students who aren’t familiar with adapting code and concepts. Design patterns are not meant to be copied precisely into source code. They are general patterns and general practices. Again, consider the chocolate chip cookie. Maybe you want to add raisins to your cookies, or replace some chocolate chips with mint chips. Maybe you’re at a higher altitude and need to add some flour. Maybe you prefer crispier cookies so you adapt the cooking temperature to a lower heat with longer time. Maybe you add another egg to the mix for a chewier cookie. As you gain skill you can customize patterns to fit your needs and wants. Maybe you decide an Observer pattern is the right way to do things, but it needs a slight change to fit your needs. Remember that the design patterns are a guide for a general process, it isn’t a strictly followed mandate.
Particularly confusing to some beginners, patterns can be implemented with other patterns. You may be implementing a state machine, where you have a current state and transitions to other states. You may decide to implement it using a command pattern, where individual nodes pass on commands to their individual systems. Those in turn may rely on a message bus pattern to communicate among the individual objects, and they may use a composition of other behaviors to put together the individual parts. I’ve used that combination in several games. Or maybe there is an AI-controlled actor in the world that wants to do something. You may start with a state machine for the AI. The system sends out a request for all the interesting game objects it can interact with. Each game object in the area may have zero or more available actions, they are composed together. It works its way back out, the individual behaviors are passed back in response to the command and the actor can choose which they want. Then they transition to the new behavior. That behavior is likely made up of other state machines, such as moving to a location, waiting to use the object, and using the object. Those in turn have submachines, traveling to a location may involve, walking, running, getting in a car (which includes walking to the car and using the car), and so on. Each of those are implemented with different patterns themselves, patterns within patterns, building an enormous structure that becomes a game.
Some patterns are more common than others. Just because you don’t know it doesn’t mean it isn’t interesting or potentially useful. There are patterns related to every domain; patterns related to databases, patterns related to computer graphics, patterns related to automated tests. There are patterns that you will never encounter, and there are patterns you’ll use frequently. If someone says that there is a pattern and you don’t know what it is, ask about it. Maybe you’ve never heard about the Curiously Recurring Template Pattern, but with a name like that how can you stop yourself from learning about it? If you learn about a pattern and never use it, that’s okay too, don’t worry about if you’ll ever use it in real life since you never know when the knowledge will be useful in something you build.
There are several patterns that beginners should take the time to learn. Here are a few.
First is the game loop. We rarely modify it and it’s one of the first things built in a game engine; even though we never touch it all the code relies on it. In a turn-based game like Tic Tac Toe the game loop waits for a player to make their mark, checks for win/lose conditions, and loops back to the beginning. In graphical games the game loop updates the game’s physics if needed, updates the simulation state if needed, updates the world logic if needed, updates the audio if needed, updates network information if needed, and renders the screen if needed; then it loops back to the beginning. Web games have a server that may listen for your command, run the processing for the command, and return the result, then loop back to listening. Learn about the game loop.
Most large games have a communication system of some kind. Generally systems can register to receive events, provide an event handler to process the events, and potentially unregister when they don’t need the events any more. There are many common patterns involved. A message bus or event bus serves as a hub for getting messages out.
Observer patterns allow a system to be notified when something happens, usually they’re closely related to communications systems. Visitor patterns allow data to move around through those systems. Dependency injection (which may sound terrifying) is registering a thing to be used, like “use this physics engine” or “notify this object when you’re done.” These patterns are everywhere.
On the flip side, there are some anti-patterns. These are patterns people have observed that tend to have problems. Sometimes the problems start slowly, so in small programs they never become painful. Other times problems evolve rapidly. These patterns also have names like “code smells”, they start to stink after a while but you may not notice them very much at first.
Probably the most popular of these is The Singleton pattern, the concept that you can only create one instance of a thing ever. For example, some people argue that a logging system should be a Singleton, that there should only be one logging instance ever. Often you want a single instance that everybody uses, but eventually you may hit a situation where somebody wants to create their own private logging instance. The Singleton is often implemented in a way that is easy to find, a static function that anybody can access. One compromise that allows for a good mix of that is a well-known instance. The well-known instance can be globally accessible either directly or through some pointers or a small global object that points to the instances, but it isn’t a Singleton because you can create more than one if you want to. Think of the standard IO streams for an example. There are the well-known streams of console input, console output, and console error, also called cin/cout/cerr, or stdin/stdout/stderr. But these are well-known instances rather than Singletons, you can create other input streams and output streams and you can direct those streams to the console if you want.
There are some other anti-patterns and code smells that are sadly common. The God Class or The Blob are objects that are all-knowing or have many different responsibilities; the counter for that is the Single Responsibility Principle that everything should be responsible for one thing. There are Dead Ends, Boat Anchors, and Lava Flows, each representing code that is no longer useful but still around; in each variation the code must make calls into the systems just in case they do something, and the behavior is difficult to change because so many systems seem to rely on it, but ultimately somebody removed the code at the other end so nothing actually happens. I’ve seen that far too often where Observers are called only to discover nobody is watching, Visitors are passed around but nobody actually uses them, the Message Bus has events broadcast but the listeners were removed ages ago.
Wrapping it up…
Patterns are fun and interesting to study, but they don’t exist for their own sake. Sometimes people declare that they want to implement a pattern, but they don’t have an actual problem to solve with it. These are patterns people noticed when solving problems. Spending time creating a state machine because you want to learn about them is fine, but realize that every state machine will be customized to the needs of the problem, so don’t spend too much time trying to craft the One True State Machine that everybody must use.
Learn about patterns, study patterns, figure out the pros and cons of common patterns, particularly the hidden costs and hidden dependencies involved with them. But in the end remember that they’re merely observations of common behavior. They don’t have absolute definitions, nor strict requirements. If one person thinks code looks like one pattern but another person thinks it looks like a different pattern, that’s okay. We all observe things differently and it’s not a reason to argue.