33
44## Intent
55
6- * Define behavior in a subclass using a set of operations provided to it by its
6+ * Define behavior in a subclass using a set of operations provided by its
77base class.*
88
99## Motivation
1010
11- Every kid has dreamed of being a superhero, but, unfortunately, cosmic rays are
11+ Every kid has dreamed of being a superhero, but unfortunately, cosmic rays are
1212in short supply here on Earth. Games that let you pretend to be a superhero are
1313the closest approximation. Because our game designers have never learned to say,
14- "no", * our* superhero game is planning to have dozens, if not hundreds of
14+ "no", * our* superhero game is planning to have dozens, if not hundreds, of
1515different superpowers that heroes may have.
1616
17- Our plan is that we'll have a ` Superpower ` base class. Then we'll have a <span
17+ Our plan is that we'll have a ` Superpower ` base class. Then, we'll have a <span
1818name="lots">derived</span > class that implements each superpower. We'll divvy up
1919the design doc among our team of programmers and get coding. When we're done,
2020we'll have a hundred superpower classes.
@@ -37,8 +37,7 @@ We want to immerse our players in a world teeming with variety. Whatever power
3737they dreamed up when they were a kid, we want in our game. That means these
3838superpower subclasses will be able to do just about everything: play sounds,
3939spawn visual effects, interact with AI, create and destroy other game entities,
40- and mess with physics. There's no corner of the codebase that won't get touched
41- by them.
40+ and mess with physics. There's no corner of the codebase that they won't touch.
4241
4342Let's say we unleash our team and get them writing superpower classes. What's
4443going to happen?
@@ -47,7 +46,7 @@ going to happen?
4746 wildly varied, we can still expect plenty of overlap. Many of them will
4847 spawn visual effects and play sounds in the same way. A freeze ray, heat
4948 ray, and Dijon mustard ray are all pretty similar when you get down to it.
50- If the people implementing those don't coordinate, that 's going to be a lot
49+ If the people implementing those don't coordinate, there 's going to be a lot
5150 of duplicate code and effort.
5251
5352 * * Every part of the game engine will get coupled to these classes.* Without
@@ -59,7 +58,7 @@ going to happen?
5958
6059 * * When these outside systems need to change, odds are good some random
6160 superpower code will get broken.* Once we have different superpower classes
62- coupling themselves to various and sundry parts of the game engine, its
61+ coupling themselves to various and sundry parts of the game engine, it's
6362 inevitable that changes to those systems will impact the power classes.
6463 That's no fun because your graphics, audio, and UI programmers probably
6564 don't want to also have to be gameplay programmers * too* .
@@ -71,18 +70,18 @@ going to happen?
7170
7271What we want is to give each of the gameplay programmers who is implementing a
7372superpower a set of primitives they can play with. You want your power to play a
74- sound, here 's your ` playSound() ` function. You want particles? Here's
73+ sound? Here 's your ` playSound() ` function. You want particles? Here's
7574` spawnParticles() ` . We'll make sure these operations cover everything you need
7675to do so that you don't need to ` #include ` random headers and nose your way into
7776the rest of the codebase.
7877
79- We do this by making these operations * protected methods of the ` Superpower `
80- base class* . Putting them in the base class gives every power subclass direct,
81- easy access to them . Making them protected (and likely non-virtual) communicates
78+ We do this by making these operations * protected methods of the* ` Superpower `
79+ * base class* . Putting them in the base class gives every power subclass direct,
80+ easy access to the methods . Making them protected (and likely non-virtual) communicates
8281that they exist specifically to be * called* by subclasses.
8382
8483Once we have these toys to play with, we need a place to use them. For that,
85- we'll define a * sandbox method* : an abstract protected method that subclasses
84+ we'll define a * sandbox method* , an abstract protected method that subclasses
8685must implement. Given those, to implement a new kind of power, you:
8786
88871 . Create a new class that inherits from ` Superpower ` .
@@ -94,12 +93,12 @@ must implement. Given those, to implement a new kind of power, you:
9493
9594We can fix our redundant code problem now by making those provided operations as
9695high-level as possible. When we see code that's duplicated between lots of the
97- subclasses, we can always roll that up into ` Superpower ` as a new operation that
96+ subclasses, we can always roll it up into ` Superpower ` as a new operation that
9897they can all use.
9998
10099We've addressed our coupling problem by constraining the coupling to one place.
101100` Superpower ` itself will end up coupled to the different game systems, but our
102- hundred derived classes are not. Instead, they are * only* coupled to their base
101+ hundred derived classes will not. Instead, they are * only* coupled to their base
103102class. When one of those game systems changes, modification to ` Superpower ` may
104103be necessary, but dozens of subclasses shouldn't have to be touched.
105104
@@ -122,13 +121,13 @@ a codebase than the one between a base class and its subclass -- but I find
122121## The Pattern
123122
124123A ** base class** defines an abstract ** sandbox method** and several ** provided
125- operations** . Marking them protected makes it clear to that they are for use by
124+ operations** . Marking them protected makes it clear that they are for use by
126125derived classes. Each derived ** sandboxed subclass** implements the sandbox
127126method using the provided operations.
128127
129128## When to Use It
130129
131- This is a very simple, common pattern lurking in lots of codebases, even outside
130+ The Subclass Sandbox pattern is a very simple, common pattern lurking in lots of codebases, even outside
132131of games. If you have a non-virtual protected method laying around, you're
133132probably already using something like this. Subclass Sandbox is a good fit
134133when:
@@ -171,7 +170,7 @@ class="pattern" href="component.html">Component</a> pattern can help here.
171170## Sample Code
172171
173172Because this is such a simple pattern, there isn't much to the sample code. That
174- doesn't mean it isn't useful -- the pattern is about the * intent* , and not the
173+ doesn't mean it isn't useful -- the pattern is about the * intent* , not the
175174complexity of its implementation.
176175
177176We'll start with our ` Superpower ` base class:
@@ -180,15 +179,15 @@ We'll start with our `Superpower` base class:
180179
181180The ` activate() ` method is the sandbox method. Since it is virtual and abstract,
182181subclasses * must* override it. This makes it clear to someone creating a power
183- class where their work has to go.
182+ subclass where their work has to go.
184183
185- The other protected methods, ` move() ` , ` playSound() ` , and ` spawnParticles() ` are
184+ The other protected methods, ` move() ` , ` playSound() ` , and ` spawnParticles() ` , are
186185the provided operations. These are what the subclasses will call in their
187186implementation of ` activate() ` .
188187
189188We didn't implement the provided operations in this example, but an actual game
190189would have real code there. Those methods are where ` Superpower ` gets coupled to
191- other systems in the game: ` move() ` may call into physics code, ` playSound() `
190+ other systems in the game -- ` move() ` may call into physics code, ` playSound() `
192191will talk to the audio engine, etc. Since this is all in the * implementation* of
193192the base class, it keeps that coupling encapsulated within ` Superpower ` itself.
194193
@@ -207,11 +206,11 @@ things basic here.
207206
208207This power springs the superhero into the air, playing an appropriate sound and
209208kicking up a little cloud of dust. If all of the superpowers were this simple --
210- just combination of a sound, particle affect , and motion -- then we wouldn't
211- need this pattern at all. Instead, ` Superpower ` could just have a baked-in
209+ just a combination of sound, particle effect , and motion -- then we wouldn't
210+ need this pattern at all. Instead, ` Superpower ` could have a baked-in
212211implementation of ` activate() ` that accesses fields for the sound ID, particle
213212type, and movement. But that only works when every power essentially works the
214- same way with just some differences in data. Let's elaborate it a bit.
213+ same way with only some differences in data. Let's elaborate on it a bit:
215214
216215^code 3
217216
@@ -220,24 +219,24 @@ Here, we've added a couple of methods to get the hero's position. Our
220219
221220^code 4
222221
223- Since we have access to some state, now our sandbox method can do actual
224- interesting control flow. Here it's still just a couple of simple ` if `
222+ Since we have access to some state, now our sandbox method can do actual,
223+ interesting control flow. Here, it's still just a couple of simple ` if `
225224statements, but you can do <span name =" data " >anything</span > you want. By having
226225the sandbox method be an actual full-fledged method that contains arbitrary
227226code, the sky's the limit.
228227
229228<aside name =" data " >
230229
231- Earlier I suggested a data-driven approach for powers. This is one reason why
232- you may decide to * not* do that. If your behavior is complex and imperative,
233- that makes it more difficult to define in data.
230+ Earlier, I suggested a data-driven approach for powers. This is one reason why
231+ you may decide * not* to do that. If your behavior is complex and imperative,
232+ it is more difficult to define in data.
234233
235234</aside >
236235
237236## Design Decisions
238237
239- As you can see, this is a fairly "soft" pattern. It describes a basic idea, but
240- doesn't have a lot of detailed mechanics. That means you'll be making some
238+ As you can see, Subclass Sandbox is a fairly "soft" pattern. It describes a basic idea, but
239+ it doesn't have a lot of detailed mechanics. That means you'll be making some
241240interesting choices each time you apply it. Here are some questions to consider.
242241
243242### What operations should be provided?
@@ -264,22 +263,22 @@ Between these two points, there's a wide middle ground where some operations are
264263provided by the base class and others are accessed directly from the outside
265264system that defines it. The more operations you provide, the less coupled
266265subclasses are to outside systems, but the * more* coupled the base class is. It
267- removes coupling from the derived classes, but only by pushing that up to the
266+ removes coupling from the derived classes, but it does so by pushing that up to the
268267base class itself.
269268
270269That's a win if you have a bunch of derived classes that were all coupled to
271- some outside system. By moving that up into a provided operation, you've
272- centralized that coupling into one place: the base class. But the more you do
273- this, the bigger and harder to maintain that one class becomes.
270+ some outside system. By moving the coupling up into a provided operation, you've
271+ centralized it into one place: the base class. But the more you do this, the
272+ bigger and harder to maintain that one class becomes.
274273
275- So where to draw the line? Here's a few rules of thumb:
274+ So where should you draw the line? Here are a few rules of thumb:
276275
277276 * If a provided operation is only used by one or a few subclasses, you don't
278277 get a lot of bang for your buck. You're adding complexity to the base class,
279278 which affects everyone, but only a couple of classes benefit.
280279
281- This may be worth it for making the operation consistent with other
282- provided operations, or it may be simpler and cleaner just to let those
280+ This may be worth it to make the operation consistent with other
281+ provided operations, or it may be simpler and cleaner to let those
283282 special case subclasses call out to the external systems directly.
284283
285284 * When you call a method in some other corner of the game, it's less intrusive
@@ -289,9 +288,9 @@ So where to draw the line? Here's a few rules of thumb:
289288
290289 <aside name =" safe " >
291290
292- "Safe" is in quotes here because technically even just accessing data can
291+ "Safe" is in quotes here because technically, even just accessing data can
293292 cause problems. If your game is multi-threaded, you could read something at
294- the same time that it's being modified. If you aren't careful you can end up
293+ the same time that it's being modified. If you aren't careful, you could end up
295294 with bogus data.
296295
297296 Another nasty case is if your game state is strictly deterministic (which
@@ -306,11 +305,11 @@ So where to draw the line? Here's a few rules of thumb:
306305 makes them good candidates for being rolled up into provided operations in
307306 the more visible base class.
308307
309- * If the implementation of a provided operation just forwards a call to some
308+ * If the implementation of a provided operation only forwards a call to some
310309 outside system, then it isn't adding much value. In that case, it may be
311- simpler to just call the outside method directly.
310+ simpler to call the outside method directly.
312311
313- However, even simple forwarding can still be useful: those methods often
312+ However, even simple forwarding can still be useful -- those methods often
314313 access state that the base class doesn't want to directly expose to
315314 subclasses. For example, let's say ` Superpower ` provided this:
316315
@@ -338,7 +337,7 @@ functionality:
338337
339338^code 7
340339
341- Then ` Superpower ` just provides access to it:
340+ Then ` Superpower ` provides access to it:
342341
343342^code 8
344343
@@ -355,10 +354,10 @@ things for you:
355354 breaking things.
356355
357356 * * It lowers the coupling between the base class and other systems.* When
358- ` playSound() ` was a method directly on ` Superpower ` , that meant our base
357+ ` playSound() ` was a method directly on ` Superpower ` , our base
359358 class was directly tied to ` SoundId ` and whatever audio code the
360359 implementation called into. Moving that over to ` SoundPlayer ` reduces
361- ` Superpower ` 's coupling to just that single ` SoundPlayer ` class, which then
360+ ` Superpower ` 's coupling to the single ` SoundPlayer ` class, which then
362361 encapsulates all of its other dependencies.
363362
364363### How does the base class get the state that it needs?
@@ -393,8 +392,8 @@ particle system object, how would it get one?
393392
394393 To avoid passing everything through the constructor, we can split
395394 initialization into two steps. The constructor will take no parameters and
396- just create the object. Then we call a separate method defined directly on
397- the base class to pass in the rest of the data that it needs.
395+ just create the object. Then, we call a separate method defined directly on
396+ the base class to pass in the rest of the data that it needs:
398397
399398 ^code 9
400399
@@ -426,19 +425,19 @@ particle system object, how would it get one?
426425 with a particle system. That makes sense when every power needs its own
427426 unique state. But let's say that the particle system is a <a class="pattern"
428427 href="singleton.html">Singleton</a >, and every power will be sharing the
429- same one .
428+ same state .
430429
431- In that case, we can make the state private to the base class, but also make
430+ In that case, we can make the state private to the base class and also make
432431 it <span name =" singleton " >* static* </span >. The game will still have to make
433432 sure that it initializes the state, but it only has to initialize the
434433 ` Superpower ` * class* once for the entire game, and not each instance.
435434
436435 <aside name =" singleton " >
437436
438- Keep in mind that this still has many of the problems of a singleton: you 've
437+ Keep in mind that this still has many of the problems of a singleton. You 've
439438 got some state shared between lots and lots of objects (all of the
440439 ` Superpower ` instances). The particle system is encapsulated, so it isn't
441- globally * visible* which is good, but it can still make reasoning about
440+ globally * visible* , which is good, but it can still make reasoning about
442441 powers harder because they can all poke at the same object.
443442
444443 </aside >
@@ -448,25 +447,25 @@ particle system object, how would it get one?
448447 Note here that ` init() ` and ` particles_ ` are both static. As long as the
449448 game calls ` Superpower::init() ` once early on, every power can access the
450449 particle system. At the same time, ` Superpower ` instances can be created
451- freely just by calling the right derived class's constructor.
450+ freely by calling the right derived class's constructor.
452451
453452 Even better, now that ` particles_ ` is a * static* variable, we don't have to
454453 store it for each instance of ` Superpower ` , so we've made the class use less
455454 memory.
456455
457456 * ** Use a service locator:**
458457
459- The previous option requires that outside code specifically remember to push
458+ The previous option requires that outside code specifically remembers to push
460459 in the state that the base class needs before it needs it. That places the
461460 burden of initialization on the surrounding code. Another option is to let
462461 the base class handle it by pulling in the state it needs. One way to do
463462 that is by using a <a class =" pattern " href =" service-locator.html " >Service
464- Locator</a >.
463+ Locator</a >:
465464
466465 ^code 12
467466
468467 Here, ` spawnParticles() ` needs a particle system. Instead of being * given*
469- one by outside code, it just fetches one itself from the service locator.
468+ one by outside code, it fetches one itself from the service locator.
470469
471470## See Also
472471
0 commit comments