Skip to content

Commit 03bdc3b

Browse files
committed
Merge branch 'laurenbee-master'
2 parents 69bce24 + fb783f7 commit 03bdc3b

File tree

2 files changed

+110
-112
lines changed

2 files changed

+110
-112
lines changed

book/subclass-sandbox.markdown

Lines changed: 55 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
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
77
base 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
1212
in short supply here on Earth. Games that let you pretend to be a superhero are
1313
the 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
1515
different 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
1818
name="lots">derived</span> class that implements each superpower. We'll divvy up
1919
the design doc among our team of programmers and get coding. When we're done,
2020
we'll have a hundred superpower classes.
@@ -37,8 +37,7 @@ We want to immerse our players in a world teeming with variety. Whatever power
3737
they dreamed up when they were a kid, we want in our game. That means these
3838
superpower subclasses will be able to do just about everything: play sounds,
3939
spawn 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

4342
Let's say we unleash our team and get them writing superpower classes. What's
4443
going 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

7271
What we want is to give each of the gameplay programmers who is implementing a
7372
superpower 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
7675
to do so that you don't need to `#include` random headers and nose your way into
7776
the 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
8281
that they exist specifically to be *called* by subclasses.
8382

8483
Once 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
8685
must implement. Given those, to implement a new kind of power, you:
8786

8887
1. 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

9594
We can fix our redundant code problem now by making those provided operations as
9695
high-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
9897
they can all use.
9998

10099
We'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
103102
class. When one of those game systems changes, modification to `Superpower` may
104103
be 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

124123
A **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
126125
derived classes. Each derived **sandboxed subclass** implements the sandbox
127126
method 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
132131
of games. If you have a non-virtual protected method laying around, you're
133132
probably already using something like this. Subclass Sandbox is a good fit
134133
when:
@@ -171,7 +170,7 @@ class="pattern" href="component.html">Component</a> pattern can help here.
171170
## Sample Code
172171

173172
Because 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
175174
complexity of its implementation.
176175

177176
We'll start with our `Superpower` base class:
@@ -180,15 +179,15 @@ We'll start with our `Superpower` base class:
180179

181180
The `activate()` method is the sandbox method. Since it is virtual and abstract,
182181
subclasses *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
186185
the provided operations. These are what the subclasses will call in their
187186
implementation of `activate()`.
188187

189188
We didn't implement the provided operations in this example, but an actual game
190189
would 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()`
192191
will talk to the audio engine, etc. Since this is all in the *implementation* of
193192
the base class, it keeps that coupling encapsulated within `Superpower` itself.
194193

@@ -207,11 +206,11 @@ things basic here.
207206

208207
This power springs the superhero into the air, playing an appropriate sound and
209208
kicking 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
212211
implementation of `activate()` that accesses fields for the sound ID, particle
213212
type, 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`
225224
statements, but you can do <span name="data">anything</span> you want. By having
226225
the sandbox method be an actual full-fledged method that contains arbitrary
227226
code, 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
241240
interesting 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
264263
provided by the base class and others are accessed directly from the outside
265264
system that defines it. The more operations you provide, the less coupled
266265
subclasses 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
268267
base class itself.
269268

270269
That'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

Comments
 (0)