The subclass method behavioral pattern allows many different subclasses to define their own behavior while reusing common functionality from the base class.
This is a simple pattern with a subtle intent and usage. It’s very much the opposite of the template method design pattern.
While the template method pattern defines a normal method in a base class that sets the overall structure of what the method should do and lets derived classes fill in specific details by overriding protected virtual methods, this subclass method pattern defines a pure virtual method in a base class that derived classes must override. It then provides non-virtual protected methods that offer common and useful functionality for the derived classes to use.
You can use this pattern anytime you have many different derived classes that all need to implement uniques but related behavior while avoiding code duplication by putting common functionality in helper methods in the base class. Putting the common behavior in one place also helps reduce coupling between each of the derived classes and the rest of the application code. the derived classes are coupled to the base class and it’s the base class that’s then coupled to the rest of the code.
This pattern also works well with the interpreter design pattern. You can use this pattern to define major behavior differences and the interpreter design pattern to customize the behavior with data. Listen to the full episode or read the full transcript below.
The basic description says that this pattern allows behavior to be specified in subclasses using operations provided by a base class.
We’ve actually finished going through all the patterns in the Gang of Four book. There’s a really good book called Game Programming Patterns by Robert Nystrom that I recommend that documents several more patterns applicable to game development. This design pattern is actually called the subclass sandbox pattern in this book.
I normally don’t like renaming patterns because one of the benefits of design patterns is the common vocabulary. Just mention to another developer that you’re using the factory pattern and you’ll be understood without needing to explain further.
Some patterns do have alternate names and I’ve mentioned already in episode 77 that I don’t really like the name of the observer pattern. This pattern is another example where I think the name is misleading. Subclass sandbox has a nice ring to it but that’s all. There is no sandbox. At least not in the sense that the term sandbox normally means.
If you ever played with a sandbox when you were little under the watchful eye of an adult, then you probably heard many times to keep the sand inside the box. Kids naturally want to fling sand everywhere and adults cringe at the mess they’ll have to clean.
In computer terms, a sandbox is a protected area that your code cannot leave. It’s used for maintaining security. And that’s where I think the name becomes misleading. This pattern has nothing to do with security.
As with a lot of patterns, there’s nothing magical or special about your code if you follow this pattern. It’s really just a base class and a bunch of derived classes. Simple inheritance. Listen to the episodes 24 through 30 if you want more information about inheritance. The difference is in your intent. When you follow this pattern, it’s because you have a reason and want to establish certain relationships between your base class, derived classes, and the rest of your code. What are those relationships?
In many ways, this design pattern is the opposite of the template method design pattern described in episode 80. That’s why I’m calling this the subclass method design pattern.
It works really well when you have a lot of subclasses that inherit from a common base class that are all designed to perform some unique but related task.
If the overall structure of the task needs to remain fixed while the derived classes change certain key aspects, then you should use the template method. But if you want to allow the subclasses the freedom do implement a task however they want, then you just use an ordinary virtual method. You can make the method a pure virtual method in the base class so that derived classes will know that it’s a required method to implement.
Just a pure virtual method is not enough to claim that you’re using this pattern though. That’s just the beginning. But maybe I’m getting a bit ahead of myself. Let’s look at an example first of why you might want to use this design pattern.
In episode 73 about the interpreter design pattern, I described one of the more complicated patterns that creates formulas to customize behavior. And I described how you could use this to reduce the work needed to create multiple similar objects. You could use data to alter a spell to cast fireballs so that one shoots a small fireball while another shoots a large fireball. I also mentioned that while a fireball spell tends to grow in size as it gets farther away, a lightning spell tends to remain focused.
This subclass method design pattern works well with the interpreter design pattern. You see, you can use the interpreter to vary effects and use the subclass to vary more fundamental behavior that’s hard to capture in a grammar. Maybe lightning jumps from one target to another while fire just spreads out evenly. These are fundamental differences in behavior. And while the interpreter pattern can help create variations of each of these, it’ll be tough to use data alone to define such fundamental differences.
I’ll explain the other crucial aspect that makes up this design pattern right after this message from our sponsor.
( Message from Sponsor )
If a pure virtual method is just the beginning of this pattern, then what’s the rest? For this, let’s go back to the fire vs. lightning spells. Both of these spells will need to play sounds, display some flashy animations and graphics, and manipulate the environment and other characters or creatures.
And if you have a lot of these fundamentally different spell types, then they’re each going to need access to a lot of the same functionality.
What happens if the code to play sounds needs to change? You’ll have to update how you play sounds in each of these spell classes. There’s a lot of duplicate code. And since we already have a convenient base class, let’s put some helper methods in the base class to do these basic tasks. The helper methods should be protected so that only derived classes can call them.
Now, if the sound playing library needs to change, only the playSound helper method in the base class needs to be updated.
This is why this design pattern is the opposite of the template method design pattern. The template method defines a fixed structure and lets derived classes fill in the details. While this subclass method design pattern provides all the details in the base class and lets the derived classes define whatever behavior they want in their method.
I say method, because this pattern like the template method, defines a method that some other code can call to accomplish something. You can name the method whatever you want. For the spell example, maybe the method will be called activate.
Maybe you’re wondering why I didn’t decide to call the method cast? After all, isn’t that the common term for initiating a spell? Yes, and that would have tied this too closely with just spells. By calling the method activate, then this pattern can be used for spells, potions, magic rings, or even ordinary door keys. In other words, you can think of activating a key to mean using it to open a door. Activating a book could mean to read it. And activating a spell could mean to cast it. Each of these results in very different behavior but all that behavior can make use of the helper methods.
The point is that you have a virtual method that derived classes can implement however they want. And when implementing their behavior, the derived classes should make use of the helper methods defined in the base class. This gives you some serious flexibility to do almost anything while keeping the coupling to the rest of the system isolated to the helper methods in the base class. Then you can combine all this with the interpreter pattern for even more customization.