Skip to content

Commit d5f2376

Browse files
committed
Service Locator Markdown file changes
This includes proofreading edits to the Service Locator Markdown file.
1 parent f3bd541 commit d5f2376

File tree

1 file changed

+52
-52
lines changed

1 file changed

+52
-52
lines changed

book/service-locator.markdown

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,23 @@ the entire game.
1616

1717
For our example, we'll consider audio. It doesn't have quite the reach of
1818
something lower-level like a memory allocator, but it still touches a bunch of
19-
game systems: A falling rock hits the ground with a crash (physics). A sniper
19+
game systems. A falling rock hits the ground with a crash (physics). A sniper
2020
NPC fires his rifle and a shot rings out (AI). The user selects a menu item with
2121
a beep of confirmation (user interface).
2222

23-
Each of these places will need to be able to call into the audio system, with
23+
Each of these places will need to be able to call into the audio system with
2424
something like one of these:
2525

2626
^code 15
2727

2828
Either gets us where we're trying to go, but we stumbled into some sticky
2929
coupling along the way. Every place in the game calling into our audio system
30-
directly references the concrete AudioSystem class and the mechanism for
30+
directly references the concrete `AudioSystem` class and the mechanism for
3131
accessing it -- either as a static class or a <a class="gof-pattern"
32-
href="singleton.html">singleton</a>.
32+
href="Singleton.html">singleton</a>.
3333

3434
These callsites, of course, have to be coupled to *something* in order to make a
35-
sound play, but letting them poke directly at the concrete audio implementation
35+
sound play, but letting them poke at the concrete audio implementation directly
3636
is like giving a hundred strangers directions to your house just so they can
3737
drop a letter on your doorstep. Not only is it a little bit *too* personal, it's
3838
a real pain when you move and you have to tell each person the new directions.
@@ -45,7 +45,7 @@ or some other "representation" of ourselves instead. By having callers go
4545
through the book to find us, we have *a convenient single place where we control
4646
how we're found.*
4747

48-
This is the Service Locator pattern in a nutshell: it decouples code that needs
48+
This is the Service Locator pattern in a nutshell -- it decouples code that needs
4949
a service from both *who* it is (the concrete implementation type) and *where*
5050
it is (how we get to the instance of it).
5151

@@ -59,14 +59,14 @@ it.
5959

6060
## When to Use It
6161

62-
Anytime you make something globally accessible to every part of your program,
62+
Anytime you make something globally accessible,
6363
you're asking for trouble. That's the main problem with the <a
6464
class="gof-pattern" href="singleton.html">Singleton</a> pattern, and this
6565
pattern is no different. My simplest advice for when to use a service locator
6666
is: *sparingly*.
6767

6868
Instead of using a global mechanism to give some code access to an object it
69-
needs, first consider *just passing the object to it*. That's dead simple, and
69+
needs, first consider *passing the object to it instead*. That's dead simple, and
7070
it makes the coupling completely obvious. That will cover most of your needs.
7171

7272
*But...* there are some times when manually passing around an object is
@@ -81,7 +81,7 @@ through ten layers of methods just so one deeply nested call can get to it is
8181
adding needless complexity to your code.
8282

8383
In those kinds of cases, this pattern can help. As we'll see, it functions as a
84-
more flexible, more configurable cousin of the singleton. When used <span
84+
more flexible, more configurable cousin of the Singleton pattern. When used <span
8585
name="well">well</span>, it can make your codebase more flexible with little
8686
runtime cost.
8787

@@ -97,7 +97,7 @@ Singleton pattern with worse runtime performance.
9797
The core difficulty with a service locator is that it takes a dependency -- a
9898
bit of coupling between two pieces of code -- and defers wiring it up until
9999
runtime. This gives you flexibility, but the price you pay is that it's harder
100-
to understand what your dependencies are just by reading the code.
100+
to understand what your dependencies are by reading the code.
101101

102102
### The service actually has to be located
103103

@@ -110,11 +110,11 @@ guarantee that we'll always get *some* service when you need it.
110110
### The service doesn't know who is locating it
111111

112112
Since the locator is globally accessible, any code in the game could be
113-
requesting a service and then poking at it. This means that service must be able
114-
to work correctly in any circumstance. For example, a class that expects to only
115-
be used during the simulation portion of the game loop and not during rendering
113+
requesting a service and then poking at it. This means that the service must be able
114+
to work correctly in any circumstance. For example, a class that expects to
115+
be used only during the simulation portion of the game loop and not during rendering
116116
may not work as a service -- it wouldn't be able to ensure that it's being used
117-
at the right time. So, if a class expects to only be used in a certain context,
117+
at the right time. So, if a class expects to be used only in a certain context,
118118
it's safest to avoid exposing it to the entire world with this pattern.
119119

120120
## Sample Code
@@ -159,26 +159,26 @@ define:
159159
The technique this uses is called *dependency injection*, an awkward bit of
160160
jargon for a very simple idea. Say you have one class that depends on another.
161161
In our case, our `Locator` class needs an instance of the `Audio` service.
162-
Normally, the locator would be responsible for constructing that itself.
162+
Normally, the locator would be responsible for constructing that instance itself.
163163
Dependency injection instead says that outside code is responsible for
164164
*injecting* that dependency into the object that needs it.
165165

166166
</aside>
167167

168-
The static `getAudio()` function does the locating -- we can call it from
169-
anywhere in the codebase and it will give us back an instance of our `Audio`
168+
The static `getAudio()` function does the locating. We can call it from
169+
anywhere in the codebase, and it will give us back an instance of our `Audio`
170170
service to use:
171171

172172
^code 5
173173

174-
The way it "locates" is very simple: it relies on some outside code to register
175-
a service provider before any tries to use the service. When the game is
174+
The way it "locates" is very simple -- it relies on some outside code to register
175+
a service provider before any code tries to use the service. When the game is
176176
starting up, it calls some code like this:
177177

178178
^code 11
179179

180180
The key part to notice here is that our `someGameCode()` function isn't aware of
181-
the concrete `ConsoleAudio` class, just the abstract `Audio` interface. Equally
181+
the concrete `ConsoleAudio` class; it is only aware of the abstract `Audio` interface. Equally
182182
important, not even the *locator* class is coupled to the concrete service
183183
provider. The *only* place in code that knows about the actual concrete class is
184184
the initialization function that registers the service.
@@ -200,18 +200,18 @@ If the calling code doesn't check that, we're going to crash the game.
200200

201201
<aside name="temporal">
202202

203-
I sometimes hear this called "temporal coupling": two separate pieces of code
203+
I sometimes hear this called "temporal coupling" -- two separate pieces of code
204204
that must be called in the right order for the program to work correctly. All
205205
stateful software has some degree of this, but as with other kinds of coupling,
206-
reducing it makes the codebase easier to manage.
206+
reducing temporal coupling makes the codebase easier to manage.
207207

208208
</aside>
209209

210210
Fortunately, there's another design pattern called "Null Object" that we can use
211211
to address this. The basic idea is that in places where we would return `NULL`
212212
when we fail to find or create an object, we instead return a special object
213213
that implements the same interface as the desired object. Its implementation
214-
basically does nothing, but allows code that receives the object to safely
214+
basically does nothing, but it allows code that receives the object to safely
215215
continue on as if it had received a "real" one.
216216

217217
To use this, we'll define another "null" service provider:
@@ -233,8 +233,8 @@ reference is a hint to users of the code that they can expect to always get a
233233
valid object back.
234234

235235
The other thing to notice is that we're checking for `NULL` in the `provide()`
236-
function instead of the accessor. That requires us to call `initialize()` early
237-
on to make sure that the Locator initially correctly defaults to the null
236+
function instead of checking for the accessor. That requires us to call `initialize()` early
237+
on to make sure that the locator initially correctly defaults to the null
238238
provider. In return, it moves the branch out of `getAudio()`, which will save us
239239
a couple of cycles every time the service is accessed.
240240

@@ -246,7 +246,7 @@ object.
246246

247247
This is also useful for *intentionally* failing to find services. If we want to
248248
<span name="disable">disable</span> a system temporarily, we now have an easy
249-
way to do so: simply don't register a provider for the service and the locator
249+
way to do so: simply don't register a provider for the service, and the locator
250250
will default to a null provider.
251251

252252
<aside name="disable">
@@ -262,17 +262,17 @@ your blood flowing in the morning.
262262
### Logging decorator
263263

264264
Now that our system is pretty robust, let's discuss another refinement this
265-
pattern lets us do: decorated services. I'll explain with an example.
265+
pattern lets us do -- decorated services. I'll explain with an example.
266266

267267
During development, a little logging when interesting events occur can help you
268268
figure out what's going on under the hood of your game engine. If you're working
269269
on AI, you'd like to know when an entity changes AI states. If you're the sound
270270
programmer, you may want a record of every sound as it plays so you can check
271271
that they trigger in the right order.
272272

273-
The typical solution is to just litter the code with calls to some `log()`
274-
function. Unfortunately, that replaces one problem with another: now we have
275-
*too much* logging. The AI coder really doesn't care when sounds are playing,
273+
The typical solution is to litter the code with calls to some `log()`
274+
function. Unfortunately, that replaces one problem with another -- now we have
275+
*too much* logging. The AI coder doesn't care when sounds are playing,
276276
and the sound person doesn't care about AI state transitions, but now they both
277277
have to wade through each other's messages.
278278

@@ -286,26 +286,26 @@ define another audio service provider implementation like this:
286286
^code 12
287287

288288
As you can see, it wraps another audio provider and exposes the same interface.
289-
It forwards the actual audio behavior to the inner provider, but also logs each
289+
It forwards the actual audio behavior to the inner provider, but it also logs each
290290
sound call. If a programmer wants to enable audio logging, they call this:
291291

292292
^code 13
293293

294-
Now any calls to the audio service will be logged before continuing as before.
294+
Now, any calls to the audio service will be logged before continuing as before.
295295
And, of course, this plays nicely with our null service, so you can both
296296
*disable* audio and yet still log the sounds that it *would* play if sound were
297297
enabled.
298298

299299
## Design Decisions
300300

301-
We've covered a typical implementation, but there's a couple of ways that it can
302-
vary, based on differing answers to a few core questions:
301+
We've covered a typical implementation, but there are a couple of ways that it can
302+
vary based on differing answers to a few core questions:
303303

304304
### How is the service located?
305305

306306
* **Outside code registers it:**
307307

308-
This is the mechanism our sample code uses to locate the service, and is the
308+
This is the mechanism our sample code uses to locate the service, and it is the
309309
most common design I see in games:
310310

311311
* *It's fast and simple.* The `getAudio()` function simply returns a
@@ -316,11 +316,11 @@ vary, based on differing answers to a few core questions:
316316
accessing the game's controllers. We have two concrete providers: one
317317
for regular games and one for playing online. The online provider passes
318318
controller input over the network so that, to the rest of the game,
319-
remote players appear to just be using local controllers.
319+
remote players appear to be using local controllers.
320320

321321
To make this work, the online concrete provider needs to know the IP
322322
address of the other remote player. If the locator itself was
323-
constructing the object, how would it know what to pass in? The locator
323+
constructing the object, how would it know what to pass in? The `Locator`
324324
class doesn't know anything about online at all, much less some other
325325
user's IP address.
326326

@@ -361,7 +361,7 @@ vary, based on differing answers to a few core questions:
361361
unavailable.
362362

363363
* *You can't change the service easily.* This is the major downside. Since
364-
the binding happens at build time, any time you want to change the
364+
the binding happens at build time, anytime you want to change the
365365
service, you've got to recompile and restart the game.
366366

367367
* **Configure it at runtime:**
@@ -377,23 +377,23 @@ vary, based on differing answers to a few core questions:
377377
the type system at runtime. For example, we could find a class with a given
378378
name, find its constructor, and then invoke it to create an instance.
379379

380-
Dynamically-typed languages like Lisp, Smalltalk, and Python get this by
380+
Dynamically typed languages like Lisp, Smalltalk, and Python get this by
381381
their very nature, but newer static languages like C# and Java also support
382382
it.
383383

384384
</aside>
385385

386386
Typically, this means loading a configuration file that identifies the
387-
provider, then using reflection to instantiate that class at runtime. This
387+
provider and then using reflection to instantiate that class at runtime. This
388388
does a few things for us:
389389

390390
* *We can swap out the service without recompiling.* This is a little more
391-
flexible than a compile-time bound service, but not quite as flexible as
391+
flexible than a compile-time-bound service, but not quite as flexible as
392392
a registered one where you can actually change the service while the
393393
game is running.
394394

395395
* *Non-programmers can change the service.* This is nice for when the
396-
designers want to be able to turn certain game features on and off, but
396+
designers want to be able to turn certain game features on and off but
397397
aren't comfortable mucking through source code. (Or, more likely, the
398398
*coders* aren't comfortable with them mucking through it.)
399399

@@ -404,8 +404,8 @@ vary, based on differing answers to a few core questions:
404404

405405
This is one of the reasons this model is appealing over in enterprise
406406
web-land: you can deploy a single app that works on different server
407-
set-ups just by changing some configs. It's less useful in games.
408-
Console hardware is pretty well standardized, and even PC games target a
407+
setups just by changing some configs. It's less useful in games.
408+
Console hardware is pretty well-standardized, and even PC games target a
409409
certain baseline specification.
410410

411411
* *It's complex.* Unlike the previous solutions, this one is pretty
@@ -422,7 +422,7 @@ vary, based on differing answers to a few core questions:
422422
CPU cycles on something that doesn't improve the player's game
423423
experience.
424424

425-
### What happens if service could not be located?
425+
### What happens if the service can't be located?
426426

427427
* **Let the user handle it:**
428428

@@ -470,7 +470,7 @@ vary, based on differing answers to a few core questions:
470470
caller is that I will not be passed `NULL`."
471471

472472
Assertions help us track down bugs as soon as the game does something
473-
unexpected, and not later when that error finally manifests as something
473+
unexpected, not later when that error finally manifests as something
474474
visibly wrong to the user. They are fences in your codebase, corralling bugs
475475
so that they can't escape from the code that created them.
476476

@@ -479,15 +479,15 @@ vary, based on differing answers to a few core questions:
479479
If the service isn't located, the game stops before any subsequent code
480480
tries to use it. The `assert()` call there doesn't solve the problem of
481481
failing to locate the service, but it does make it clear whose problem it
482-
is. By asserting here, we say, "failing to locate a service is a bug in the
482+
is. By asserting here, we say, "Failing to locate a service is a bug in the
483483
locator."
484484

485485
So what does this do for us?
486486

487487
* *Users don't need to handle a missing service.* Since a single service
488488
may be used in hundreds of places, this can be a significant code
489489
saving. By declaring it the locator's job to always provide a service,
490-
we spare the users of it from having to pick up that slack.
490+
we spare the users of the service from having to pick up that slack.
491491

492492
* *The game is going to halt if the service can't be found.* On the off
493493
chance that a service really can't be found, the game is going to halt.
@@ -515,7 +515,7 @@ vary, based on differing answers to a few core questions:
515515
missing service. Say the game uses a service to access some data and
516516
then make a decision based on it. If we've failed to register the real
517517
service and that code gets a null service instead, the game may not
518-
behave like we want. <span name="null">It</span> will take some work to
518+
behave how we want. <span name="null">It</span> will take some work to
519519
trace that issue back to the fact that a service wasn't there when we
520520
thought it would be.
521521

@@ -534,7 +534,7 @@ chances of a service failing to be found by then are pretty slim.
534534
On a larger team, I encourage you to throw a null service in. It doesn't take
535535
much effort to implement, and can spare you from some downtime during
536536
development when a service isn't available. It also gives you an easy way to
537-
turn a service off if it's buggy or is just distracting you from what you're
537+
turn off a service if it's buggy or is just distracting you from what you're
538538
working on.
539539

540540
### What is the scope of the service?
@@ -558,9 +558,9 @@ There are advantages either way:
558558
one.
559559

560560
* *We lose control over where and when the service is used.* This is the
561-
obvious cost of making something global: anything can get to it. The <a
561+
obvious cost of making something global -- anything can get to it. The <a
562562
class="gof-pattern" href="singleton.html">Singleton</a> chapter has a
563-
full cast of characters for the horrorshow that global scope can spawn.
563+
full cast of characters for the horror show that global scope can spawn.
564564

565565
* **If access is restricted to a class:**
566566

0 commit comments

Comments
 (0)