Skip to content

Commit 1042601

Browse files
committed
fixes
1 parent 4eac144 commit 1042601

File tree

3 files changed

+22
-17
lines changed

3 files changed

+22
-17
lines changed

chapters/ch02.asciidoc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ The following example shows an email component that's only concerned with config
8787
----
8888
import mailApi from 'mail-api'
8989
import { mailApiSecret } from './secrets'
90-
export default function send (options, done) {
90+
91+
export default function send(options, done) {
9192
const { to, subject, html } = options
9293
const client = mailApi({ mailApiSecret })
9394
client.send({
@@ -103,7 +104,7 @@ It wouldn't be hard to create a drop-in replacement by developing a module which
103104

104105
[source,javascript]
105106
----
106-
export default function send (options, done) {
107+
export default function send(options, done) {
107108
const { to, subject, html } = options
108109
console.log(`
109110
Sending email.
@@ -120,12 +121,14 @@ By the same token, a templating component could be developed orthogonally, with
120121
[source,javascript]
121122
----
122123
import insane from 'insane'
123-
function sanitize (template, ...expressions) {
124+
125+
function sanitize(template, ...expressions) {
124126
return template.reduce((result, part, i) =>
125127
result + insane(expressions[i - 1]) + part
126128
)
127129
}
128-
export default function compile (model) {
130+
131+
export default function compile(model) {
129132
const { title, body, tags } = model
130133
const html = sanitize`
131134
<h1>${ title }</h1>
@@ -142,7 +145,9 @@ export default function compile (model) {
142145
}
143146
----
144147

145-
Changing the API slightly shouldn't be an issue, as long as the API remains consistent across the components we want to make interchangeable. A different implementation could take a template identifier, in addition to the +model+ object, so that the template itself is also decoupled from the +compile+ function.
148+
Slightly modifying the API shouldn't be an issue, as long as it remains consistent across the components we want to make interchangeable. For instance, a different implementation could take a template identifier, in addition to the +model+ object, so that the template itself is also decoupled from the +compile+ function.
149+
150+
When we keep the API consistent across implementationsfootnoteref:[impl-examples, For example, one implementation might merely compile an HTML email using inline templates, another might use HTML template files, another could rely on a third party service, and yet another could compile emails as plain-text instead.], using the same signature across every module, it's easy to swap out implementations depending on context such as the execution environment (development vs. staging vs. production) or any other dynamic context that we need to rely upon.
146151

147152
As we mentioned earlier, a third module could plumb together different components which handle separate concerns, such as templating and email sending. The following example leverages the logging email provider and the static templating function to join both concerns together. Interestingly, this module doesn't break SRP either, as its only concern is to plumb other modules together.
148153

chapters/ch05.asciidoc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,11 @@ The introduction of a `class` keyword, paired with the React framework originall
333333

334334
Inheritance is also useful when there's an abstract interface to implement and methods to override, particularly when the objects being represented can be mapped to the real world. In practical terms and in the case of JavaScript, inheritance works great when the prototype being extended offers a good description for the parent prototype: a `Car` is a `Vehicle` but a car is not a `SteeringWheel`: the wheel is just one aspect of the car.
335335

336-
==== 5.2.2 The Perks of Composition: Aspects and Decorators
336+
==== 5.2.2 The Perks of Composition: Aspects and Extensions
337337

338338
With inheritance we can add layers of complexity to an object. These layers are meant to be ordered: we start with the least specific foundational bits of the object and build our way up to the most specific aspects of it. When we write code based on inheritance chains, complexity is spread across the different classes, but lies mostly at the foundational layers which offer a terse API while hiding this complexity away. Composition is an alternative to inheritance. Rather than building objects by vertically stacking functionality, composition relies on stringing together orthogonal aspects of functionality. In this sense, orthogonality means that the bits of functionality we compose together complements each other, but doesn't alter one another's behavior.
339339

340-
One way to compose functionality is additive: we could write decorators, which augment existing objects with new functionality. In the following code snippet we have a `makeEmitter` function which adds flexible event handling functionality to any target object, providing them with an `.on` method, where we can add event listeners to the target object; and an `.emit` method, where the consumer can indicate a type of event and any number of parameters to be passed to event listeners.
340+
One way to compose functionality is additive: we could write extension functions, which augment existing objects with new functionality. In the following code snippet we have a `makeEmitter` function which adds flexible event handling functionality to any target object, providing them with an `.on` method, where we can add event listeners to the target object; and an `.emit` method, where the consumer can indicate a type of event and any number of parameters to be passed to event listeners.
341341

342342
[source,javascript]
343343
----
@@ -380,7 +380,7 @@ person.emit('move', 23, 5)
380380

381381
This approach is versatile, helping us add event emission functionality to any object without the need for adding an `EventEmitter` class somewhere in the prototype chain of the object. This is useful in cases where you don't own the base class, when the targets aren't class-based, or when the functionality to be added isn't meant to be part of every instance of a class: there are persons who emit events and persons that are quiet and don't need this functionality.
382382

383-
Another way of doing composition, that doesn't rely on decorators, is to rely on functional aspects instead, without mutating your target object. In the following snippet we do just that: we have an `emitters` map where we store target objects and map them to the event listeners they have, an `onEvent` function that associates event listeners to target objects, and an `emitEvent` function that fires all event listeners of a given type for a target object, passing the provided parameters. All of this is accomplished in such a way that there's no need to modify the `person` object in order to have event handling capabilities associated with the object.
383+
Another way of doing composition, that doesn't rely on extension functions, is to rely on functional aspects instead, without mutating your target object. In the following snippet we do just that: we have an `emitters` map where we store target objects and map them to the event listeners they have, an `onEvent` function that associates event listeners to target objects, and an `emitEvent` function that fires all event listeners of a given type for a target object, passing the provided parameters. All of this is accomplished in such a way that there's no need to modify the `person` object in order to have event handling capabilities associated with the object.
384384

385385
[source,javascript]
386386
----
@@ -431,7 +431,7 @@ emitEvent(person, 'move', 23, 5)
431431

432432
Note how we're using both `WeakMap` and `Map` here. Using a plain `Map` would prevent garbage collection from cleaning things up when `target` is only being referenced by `Map` entries, whereas `WeakMap` allows garbage collection to take place on the objects that make up its keys. Given we usually want to attach events to objects, we can use `WeakMap` as a way to avoid creating strong references that might end up causing memory leaks. On the other hand, it's okay to use a regular `Map` for the event listeners, given those are associated to an event type string.
433433

434-
Let's move onto deciding whether to use inheritance, decorators, or functional composition, where each pattern shines, and when to avoid them.
434+
Let's move onto deciding whether to use inheritance, extension functions, or functional composition, where each pattern shines, and when to avoid them.
435435

436436
==== 5.2.3 Choosing between Composition and Inheritance
437437

@@ -441,7 +441,7 @@ Decoration and functional composition are friendlier patterns because they aren'
441441

442442
The functional approach is a bit more cumbersome to implement than simply mutating objects or adding base classes, but it offers the most flexibility. By avoiding changes to the underlying target, we keep objects easy to serialize into JSON, unencumbered by a growing collection of methods, and thus more readily compatible across our codebase.
443443

444-
Furthermore, using base classes makes it a bit hard to reuse the logic at varying insertion points in our prototype chains. Using decorators, likewise, makes it challenging to add similar methods that support slightly different use cases. Using a functional approach leads to less coupling in this regard, but it could also complicate the underlying implementation of the makeup for objects, making it hard to decypher how their functionality ties in together, tainting our fundamental understanding of how code flows and making debugging sessions longer than need be.
444+
Furthermore, using base classes makes it a bit hard to reuse the logic at varying insertion points in our prototype chains. Using extension functions, likewise, makes it challenging to add similar methods that support slightly different use cases. Using a functional approach leads to less coupling in this regard, but it could also complicate the underlying implementation of the makeup for objects, making it hard to decypher how their functionality ties in together, tainting our fundamental understanding of how code flows and making debugging sessions longer than need be.
445445

446446
As with most things programming, your codebase benefits from a semblance of consistency. Even if you use all three patterns, -- and others -- a codebase that uses half a dozen patterns in equal amounts is harder to understand, work with, and build on, than an equivalent codebase that instead uses one pattern for the vast majority of its code while using other patterns in smaller ways when warranted. Picking the right pattern for each situation and striving for consistency might seem at odds with each other, but this is again a balancing act. The trade-off is between consistency in the grand scale of our codebase versus simplicity in the local piece of code we're working on. The question to ask is then: are we obtaining enough of a simplicity gain that it warrants the sacrifice of some consistency?
447447

0 commit comments

Comments
 (0)