WHAT’S IN THIS CHAPTER?
WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
The wrox.com code downloads for this chapter are found at www.wrox.com/go/reliablejavascript
on the Download Code tab. The files are in the Chapter 21 download and individually named according to the filenames noted throughout this chapter.
Most programs you have written were probably written to achieve some goal. The architecture may have been the conventional, top-down variety that you learned along with object-oriented programming. The architectures you will consider in this chapter are different. They allow independent parts to interact in a decentralized manner. The overall goal happens as if by magic.
The patterns in question are known as the Observer and Mediator. They are similar in that they are decentralized, but the means of communication differ.
If you’ve subscribed to daily newspaper delivery, you’ve participated in a real-life implementation of the Observer Pattern. By sending your subscription card to the newspaper publisher, you alerted the publisher that you would like to have the paper delivered to your home every day.
Once your address is on the publisher’s list, there’s nothing more that you need to do. When the paper is ready each day, the publisher dispatches a copy to your address and each of the other addresses in its list.
If you decide to discontinue daily delivery, all you must do is ask the publisher to remove your address from its list of addresses. The list of addresses the publisher maintains regularly grows and shrinks as new subscriptions are added and existing subscriptions are cancelled.
An important aspect of your relationship with the publisher is that all the newspaper publisher knows about you, or any of its other subscribers, is the address to which the paper should be delivered. The publisher doesn’t know if your home is blue, if it’s made of brick, or if it has three floors. The delivery of the newspaper doesn’t rely on any of those aspects of your home. You can change your home’s color or add a floor, and the paper will continue to show up.
If you describe the newspaper subscription example with the terminology of the intent of the pattern, as described by Design Patterns, you find the following:
One of the goals of developers of reliable software is to minimize the number and scope of dependencies between objects. You may have noticed that intent of the pattern as described by Design Patterns uses a derivative of the word “dependency” not once, but thrice. Does that mean it should be avoided? Definitely not; it should be embraced. You can depend on it to help you write loosely coupled, reliable code.
In fact, one of the most appealing characteristics of the Observer Pattern is that the dependency between the subject, or object that changes state, and its observers, the dependents that are notified of the subject’s state change, is well defined and narrow.
In this section, we show you how use of the Observer Pattern leads to loosely coupled code and also provide techniques for enhancing the reliability of implementations of the pattern.
The Observer Pattern is made up of two pieces:
In order to support observation, the subject must provide:
Additionally, the observer must provide a method the subject can call to alert the observer to a change.
Suppose that the organizers of the JavaScript conference have requested the capability to see information about attendee registrations. They’d like the capability to display the data in two ways:
As an additional wrinkle, the organizers would like the information to update in near real-time without requiring a browser refresh.
You decide that it would be appropriate to create two modules: one that displays the count and one that displays the names of the recent registrants. The module that displays the count could periodically poll the server to find out if there are any new registrants, and the module that displays the names could also periodically poll the server to find out if there are any new registrants.
“You could write a separate module that’s responsible for polling for new registrants, and it can update the two modules responsible for displaying the information to the user,” Charlotte suggests (can she read your mind?). “The polling module could allow the other modules to register for updates; it would be the subject in the Observer Pattern. The display modules would be the observers.”
Charlotte’s suggestion is appealing for many reasons. First, it nicely separates the concerns of retrieving updates from the server and displaying results to the user. Second, while there is a dependency between the display modules and the data retrieval module, it’s very narrow; it’s only the method on the observer that the subject will call to alert the observer to a change.
Your first task is to create the module that will act as the subject in this implementation of the Observer Pattern: the recentRegistrationsService
. This module is responsible for periodically polling the server and retrieving the attendees that have registered since the last time it polled. Also, because it’s participating in the Observer Pattern as a subject, the recentRegistrationsService
must also provide the ability for observers to subscribe and unsubscribe, and the ability to notify each of the observers when a new attendee has registered.
The implementation of the recentRegistrationsService
follows in Listing 21-1.
The recentRegistrationsService
module provides the public methods shown in Table 21.1 that allow it to act as the subject in the Observer Pattern.
Table 21.1 Table 21-1: Methods Allowing recentRegistrationsService as the Subject
METHOD | DESCRIPTION |
addObserver(observer) |
Adds observer to the list of observers that will receive an update each time an attendee registers |
removeObserver(observer) |
Removes observer from the list of observers so that it will no longer receive an update each time an attendee registers |
updateObservers(newAttendee) |
Executes each observer’s update method, providing newAttendee as an argument |
Along with the methods that are required to act as a subject, the recentRegistrationsService
also provides some additional public methods, shown in Table 21.2, that aren’t strictly required in order to participate in the Observer Pattern.
Table 21.2 Table 21-2: Convenience Methods of recentRegistrationsService
METHOD | DESCRIPTION |
clearObservers() |
Removes all observers from the list of observers |
hasObserver(observer) |
Returns true if observer has been registered for updates from recentRegistrationsService , false otherwise |
stopPolling() |
Clears the interval set up to poll the server |
In addition to exposing the public methods described in Tables 21.1 and 21.2, the recentRegistationsService
module has a private method, getNewAttendees
, which is responsible for communicating with the server and retrieving the most recent registrants.
Just prior to returning the service instance, service
, to the caller, the following is executed:
pollingProcess = setInterval(function pollForNewAttendees(){
getNewAttendees().then(function processNewAttendees(newAttendees){
newAttendees.forEach(function updateWithNewAttendee(newAttendee){
service.updateObservers(newAttendee);
});
});
}, 15000);
The previous snippet of code sets up a recurring operation using setInterval
to execute getNewAttendees
every 15 seconds. The ID of the interval is retained in pollingProcess
so that the polling may be canceled. The promise returned by getNewAttendees
will resolve to an array with no elements if no attendees have registered in the last 15 seconds. Or, if registrations have occurred, the array will have one or more elements, each representing a newly registered attendee. For each new registration, service
’s update
method is executed with the new attendee provided as an argument.
Figure 21.1 shows that the tests all pass.
With a functional recentRegistrationsService
, the subject in this implementation of the Observer Pattern, the next step is to create the modules that will display the total registration count and the most recent ten registrations. These act as the observers in this implementation of the Observer Pattern.
To reiterate, the only thing that an observer must provide to participate in the pattern is a function that the subject can execute when there’s a change that the subject needs to notify the observer about. The recentRegistrationsService
module expects that its observer will expose a function called update
that accepts an attendee
object. As such, the totalAttendeeCount
and mostRecentRegistrations
modules will each need to implement the update
function.
Listing 21-2 provides the unit tests for the functionality of the totalAttendeeCount
module related to its role as an observer.
The three tests in Listing 21-2 verify that
totalAttendeeCount
registers itself as an observer of the injected instance of recentRegistrationsService
.getCount
method returns the initial count value provided to the module function.update
method causes the value returned by getCount
to be incremented by one.It’s important to ensure that the instance registers itself as an observer of recentRegistrationsService
because that action is required in order for the module to provide any functionality. Also, if the update
method doesn’t increment the count of attendees, the conference organizers may mistakenly think that no one is registering for the conference.
Listing 21-3 provides an implementation of totalAttendeeCount
that allows the unit tests to pass.
One thing that you should take away from Listing 21-3 is that there’s very little code required to allow the totalAttendeeCount
module to be an observer. The totalAttendeeCount
module only needs to provide an update
function that accepts an attendee
and add itself as an observer of the recentRegistrationsService
instance.
Figure 21.2 shows that the implementation of totalAttendeeCount
in Listing 21-3 allows the unit tests from Listing 21-2 to pass.
Now that both recentRegistrationsService
and totalAttendeeCount
have passing unit test suites, we’ve maxed out their reliability, right? Well no, not quite. The next section examines what more can be done to help make this implementation of the Observer Pattern defect resistant.
Consider the addObserver
function from the recentRegistrationsService
:
addObserver: function addObserver(observer){
return registeredObservers.push(observer);
}
What happens if observer
doesn’t implement the update function that’s required for it to participate in the Observer Pattern? It’s happily added to the list of registered users, that’s what. When it’s time for recentRegistrationsService
to update all of its observers, however, it will encounter the invalid observer and generate an Error
. That doesn’t sound very reliable, does it?
Adding insult to injury, debugging the problem is difficult because the error doesn’t occur until the first update is attempted. It would be far more helpful if the error occurred when the invalid observer is added.
The problem could be alleviated by adding a check to addObserver
that ensures observer
has an update
function. While that would address the issue, it isn’t reusable; any other implementations of addObserver
would need to perform the same check.
Instead, we’d rather leverage the ContractRegistry
from Chapter 17 to apply the verification in a declarative manner. That way, any future objects that act as the subject of the Observer Pattern may utilize the verification as well.
Listing 21-4 shows a unit test that ensures an error is generated when callers provide an object that doesn’t implement an update
function as the observer
argument to addObserver
.
As you might have predicted, the test is extremely simple. It provides an object without an update
function to addObserver
, and ensures that an error is thrown. Figure 21.3 shows that, while simple, the test doesn’t pass.
Implementing the check using the ContractRegistry
and contract modules from Chapter 17 is almost as simple. First, a contract module that provides the evaluator function, observerContracts
, must be created. It appears in Listing 21-5.
The getContracts
function of the observerContracts
module returns a single contract definition. The contract definition returned has a name
of “observer” and an evaluator
that ensures that thing.update
is a function. The observerContracts
module doesn’t attach any validators of its own.
Along with observerContracts
, a contract module that attaches an argument validator to the addObserver
function of the recentRegistrationsService
is required. That contract module, recentRegistrationsServiceContracts
, appears in Listing 21-6.
The meat of Listing 21-6 appears in the attachValidators
function. In attachValiadators
, the “observer” validator defined in the observerContracts
module is applied via aspect to the addObserver
function of each recentRegistrationsService
instance created.
The ConferenceContractRegistry
, which appears in Listing 21-7, defines the contract from the observerContracts
module and attaches the validator from the recentRegistrationsServiceContracts
module.
The result is that the observer validator is applied to the addObserver
function of the recentRegistrationsService
, and the new unit test passes. Figure 21.4 shows that all of the recentRegistrationsService
tests pass.
The end result is that, as long as the ConferenceContractRegistry
is included on the conference’s website (or along with the unit tests), you can be sure that an error will be generated whenever addObserver
is invoked with an argument that cannot act as an observer.
In a manner similar to that which was used to ensure addObserver
is invoked with a proper observer
object, the recentRegistrationsServiceContracts
module may be extended to validate that the argument provided to the updateObservers
function satisfies the Conference.attendee .personalInfo
contract that was created in Chapter 17. Likewise, the same may be done to ensure that the appropriate argument is provided to the totalAttendeeCount
module’s update
function.
For over 50 years, the United States and Cuba had no official relationship. The longer the parties didn’t talk, the less likely it seemed they ever would or could. Then, in 2014, Pope Francis wrote to Presidents Obama and Castro, appealing to them to “resolve humanitarian questions of common interest.” Both countries responded—not to each other, mind you, because they still weren’t talking, but to the Pope. After a few months, the leaders announced their intention to resume full diplomatic relations.
It wasn’t the first time a mediator has played a key role in history, but that initial exchange exemplified the Mediator Pattern beautifully. The mediator initiates communication and gets the parties moving. They respond to him or her, and the mediator orchestrates the next step.
In diplomacy, labor negotiations, and marriages, mediators are usually brought in to resolve trouble. In software design, the Mediator Pattern is a way to avoid it. While society works best when people talk freely with each other, a software system is the opposite: fewer lines of communication mean fewer ways to go wrong.
The pattern consists of several colleagues, which may perform different or similar tasks, and a single mediator, which orchestrates the overall effort. In its purest form, this orchestration consists only of managing the interactions between the colleagues and does not involve any business logic.
It just so happens that an opportunity to use this pattern has arisen during your work on the JavaScript conference’s website. The organizers know that the caffeine-buzzed participants will want a way to keep busy during the few minutes between sessions. They have asked you to design and write a game for this purpose.
You decide to make it a two-person game rather than a solo exercise. After all, one reason people go to conferences is to network. Not having joysticks or other gaming devices, both players will be at the same keyboard.
The game will go like this. As in Figure 21.5, a matrix of nodes will be connected by paths. Several bots (the medium-sized dots) will wander the graph. There will also be two larger dots to represent the two players. It will be the players’ task between them to encounter, and thereby knock off the board, every bot as quickly as possible. One player will use the keys 1, 2, 3 and 4 to navigate left, up, down and right, respectively. The other will use 6, 7, 8, and 9.
A game is a classic scenario for the Mediator Pattern. These are the colleagues and their responsibilities:
player
—Moves from its current node to a connected one, and informs the mediator
that it has done sobot
—Does the same as a player
gameLogic
—Contains the logic of the game, including determining when the game is over. It lays out the board in “normalized space” and puts players and bots on it. Normalized space is a square that is one unit across. It is up to the svgDisplay
(described next) to present this in the browser.svgDisplay
—Draws the game on an svg
element. Receives instructions to do so from the mediator
, but does not communicate anything back to the mediator
.And coordinating the actions of the colleagues is mediator
, which
gameLogic
(with players and bots) and an svgDisplay
.When a player or bot says it has moved, the mediator informs gameLogic
and svgDisplay
. When gameLogic
says the game is over, the mediator unhooks the event listener.
Characteristic of the Mediator Pattern, when a player
encounters a bot
, it does not evict the bot
on its own. Instead, it merely moves to the new node and informs the mediator
that it has done so. The mediator
passes this information to the gameLogic
, which detects the collision and removes the bot
. Yet even then, the gameLogic
does not adjust the display; that’s done through another message to mediator
, which then tells svgDisplay
to remove the bot
.
In this book, we have emphasized test-driven development, illustrating it with relatively small fragments of a hypothetical, larger system. For the current example, we offer the fully developed game in this chapter’s downloads, but will touch on only the highlights of the development process.
Listing 21-8 shows one of the colleagues: the player
.
Typical of the Mediator Pattern, the player
is instantiated with a mediator
already in hand (third line of the listing). The mediator
could be a complex object so this seems to introduce a broad coupling. However, you will soon see how the Interface Segregation Principle and judicious unit-testing mitigate this evil.
The simple getId
, getNode
, and setNode
methods need no explanation (although if you haven’t seen it before, you might be interested in the little trick of attaching a static variable, nextId
, to the function when the variable is declared).
The move
method is what the mediator
will wire to a keydown
event by calling the activate
function. When move
is invoked, it first checks that the requested movement is possible. If it is, it calls setNode
with the new location and then informs the mediator
that it should do whatever needs to be done when a player moves.
Although the player
object knows a little about the topography of the game (only what nodes are in the immediate vicinity), think of all the things it does not know. It doesn’t know when the game is over, nor how many other player
s there are, nor anything about bot
s. It certainly doesn’t know the game is displayed in SVG. All this ignorance is what makes player
easy to test (see Listing 21-9).
As usual, the output of successful unit tests do a great job of explaining in plain English what the object does (see Figure 21.6).
The overview is plain enough, but there are a few interesting things to notice about the tests.
First, at the very top of the listing, notice the fakeMediator
.
fakeMediator = {
onPlayerMoved: function() {}
};
A mediator
of some sort is necessary to initialize the player
(tenth line of the listing):
player = Game.player(fakeMediator);
However, if the unit tests completely cover player
’s code (which they do), and they use a fake that has only the functions needed for player
, the unit tests also verify that player
only needs those functions. This allows you to segregate the mediator’s interface, as you read in Chapter 16.
Where can you install and enforce a segregated interface? In the ContractRegistry
, of course. This was also covered in Chapter 16, expanded in Chapter 17, and used a little in Chapter 19, but this game is the first full example of its pervasive use.
Listing 21-10 shows how the mediator’s interfaces are established.
You may recall the suggestion in the “Putting It All Together” section of Chapter 17 that you could use a module like the one above to supply contracts and validators. By convention, such a module would have getContracts
and attachValidators
methods. Then, a contract registry for the application would consume the methods from all such modules to assemble the contract registry. (In this chapter’s downloads, Mediator\GameContractRegistry.js
is that auto-assembling registry. You will see more of it shortly.)
The preceding mediatorContracts.js
file has three contracts for the mediator: mediatorForPlayer
, mediatorForBot
and mediatorForLogic
. The mediatorForPlayer
interface requires only that the thing
under consideration be an object and that it have an onPlayerMoved
method.
We have put the mediatorForPlayer
contract in mediatorContracts.js
, but you might feel that it belongs in playerContracts.js
. The idea is not without merit. Suppose that the mediator
and player
modules are maintained by different people or even different teams. Now suppose that mediator
changes in a way that requires a revision to the mediatorForPlayer
contract. (This violates the Open/Closed Principle from Chapter 1, but sometimes life is not ideal.) Whoever makes the change might change the contract and think his job is done. The player
object will verify that the incoming mediator
fulfills the contract, but the contract no longer reflects player
’s original expectations!
In contrast, suppose the player
object (and its programmers) owned the mediatorForPlayer
contract. That is, suppose the contract was in playerContracts.js
instead of mediatorContracts .js
. When mediator
’s programmers make their change, they will not change the contract, which is outside of their domain. If the change breaks the contract, player
’s unit tests will fail and player
’s programmers will know they should adjust their code.
The idea that an interface is owned by the module that consumes it rather than by the module that fulfills it is part of some formulations of the Dependency Inversion Principle. The main disadvantage is that every module that consumes the interface must duplicate its description. That violates the DRY Principle.
So you have a choice. You can put contracts like mediatorForPlayer
in mediatorContracts.js
so mediator
’s programmers know what might be a breaking change, or you can put them in the contract modules for the consuming objects so their unit tests will fail appropriately when mediator
changes.
Wherever the contract resides, the next step is to put it to use.
The mediatorContracts.js
module created a mediatorForPlayer
interface. Now playerContracts.js
can ensure that whenever a player
is created, it gets such a mediator
(see Listing 21-11):
The first two lines of the attachValidators
method cause the mediatorForPlayer
contract to be checked against the argument to Game.player
:
registry.attachArgumentsValidator(
'player', Game, ['mediatorForPlayer']);
If something else is passed in, an exception is thrown. In return, player
promises that it will return an object that meets the 'player'
contract:
registry.attachReturnValidator(
'player', Game, 'player');
That contract is a promise to provide the methods you saw in player
:
function isPlayer(thing) {
return typeof thing === 'object' &&
typeof thing.getId === 'function' &&
typeof thing.setNode === 'function' &&
typeof thing.getNode === 'function' &&
typeof thing.activate === 'function' &&
typeof thing.deactivate === 'function';
}
Importantly, it is not a guarantee of an actual player object (using instanceof
, for example). Contracts that use characteristics instead of types keep your code in the JavaScript “duck-typing” idiom. In fact, you have already seen how this was useful when creating the fakeMediator
in Listing 21-9.
Code contracts do add several layers of function-calling to each method they protect. Not only does that impede performance, but it can make debugging more complex. If you would like to remove code contracts for production or temporarily during debugging, it’s as easy as removing this line from index.html
:
<script src="GameContractRegistry.js"></script>
Without that line, the contract registry is never instantiated, much less populated, and contracts will have absolutely no footprint in your running code. (They don’t run, but to keep them from loading, you can also remove the <script>
tags for the contract modules, too.) For reference, Listing 21-12 is what you would be missing.
The other colleagues in the game are similar in spirit. You can find their code and unit tests in this chapter’s downloads, in the Mediator
directory.
The mediator is not a mastermind. Its role is just to get things started, make sure that every object knows what it needs to know until they decide to call it quits, and then close things down in an orderly way. Listing 21-13 shows the mediator
for the game.
When the mediator
is instantiated, it creates the gameLogic
and svgDisplay
colleagues (at the end of the listing). The gameLogic
, in turn, creates the player
s and the bot
s.
The mediator that other modules will see is in the med
variable midway down the listing and returned at the end.
As you continue to look at the player
colleague, you see that the flow of control goes like this:
activate
method adds an event listener that will cause the appropriate player
’s move
method to be called when the right key is pressed.player.move
, upon a successful transfer to a new node, calls mediator .onPlayerMoved
.gameLogic
object detects that the game is over and calls mediator.endGame
, which will unhook the event listener.The mediator’s job involves lots of interaction with objects it’s not supposed to know a great deal about, so you can guess that Jasmine spies will abound. Listing 21-14 shows some of the details.
In the early part of the listing, fakes are set up for the display, the players, and the game logic. All the mediator
’s tests care about is that these objects are called as expected, and spies are created for that purpose in the next part of the listing:
spyOn(Game,'svgDisplay').and.returnValue(fakeDisplay);
spyOn(Game,'gameLogic').and.returnValue(fakeLogic);
spyOn(fakeDisplay,'onPlayerMoved');
spyOn(fakeDisplay,'endGame');
spyOn(fakeLogic,'onPlayerMoved');
The first pair of spies intercept the creation of svgDisplay
and gameLogic
, causing the fakes to be returned. The remaining spies will detect when onPlayerMoved
is called on those objects, and when the game ends. There is no callThrough
, callFake
, or returnValue
on those spies because the only matter of interest is whether mediator
has made the calls.
The tests of onPlayerMoved
demonstrate how this plays out:
describe('onPlayerMoved(player)', function() {
var player;
beforeEach(function() {
var mediator = Game.mediator(),
node = Game.gameNode();
player = Game.player(mediator);
player.setNode(node); // Pretend just moved here.
mediator.onPlayerMoved(player);
});
it("informs the board of the player's new location", function() {
expect(fakeLogic.onPlayerMoved).toHaveBeenCalledWith(player);
});
it("informs the display of the player's new location", function() {
expect(fakeDisplay.onPlayerMoved).toHaveBeenCalledWith(player);
});
});
The expectations are simply that the spies were called with the correct parameters. This is typical when unit-testing a mediator. If the tests are more complicated than just verifying that the mediator makes the appropriate calls to the appropriate colleagues, your implementation of the Mediator Pattern may not be as pure as you might wish.
The Observer and Mediator Patterns are closely related and their reliable development has much in common, including the following:
The next section of this book considers a few special or exotic subjects in testing.