Skip to content

Commit 1207b53

Browse files
committed
First post
1 parent 17cc298 commit 1207b53

File tree

3 files changed

+205
-2
lines changed

3 files changed

+205
-2
lines changed

_config.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ remote_theme: "mmistakes/minimal-mistakes"
5959
# Minimal Mistakes Theme configuration 👇
6060
# https://github.com/mmistakes/minimal-mistakes/blob/master/_config.yml
6161
locale: en
62-
date_format: "%Y-%m-%d"
62+
date_format: "%B %-d, %Y"
6363
enable_copy_code_button: true
6464
atom_feed:
6565
hide: true
@@ -98,7 +98,7 @@ defaults:
9898
layout: single
9999
author_profile: false
100100
breadcrumbs: false
101-
show_date: false
101+
show_date: true
102102
read_time: false
103103
comments: false
104104
share: false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
---
2+
title: "Encapsulating Collections in Javascript"
3+
---
4+
5+
Imagine you have the following class:
6+
7+
```js
8+
class Team {
9+
constructor(members) {
10+
this._members = members;
11+
}
12+
13+
get members() {
14+
return this._members;
15+
}
16+
17+
set members(members) {
18+
this._members = members;
19+
}
20+
}
21+
```
22+
23+
In your code, you could interact with this class like this:
24+
25+
```js
26+
const team = new Team([mike, john]);
27+
28+
// Add a member
29+
team.members.push(peter);
30+
31+
// Remove a member
32+
const index = team.members.indexOf(peter);
33+
team.members.splice(index, 1);
34+
35+
// Replace the members
36+
team.members = [mike, john, peter];
37+
38+
// Get the number of members
39+
console.log(team.members.length);
40+
41+
// Iterate through the members
42+
for (let member of team.members) {
43+
console.log(member.fullname);
44+
}
45+
```
46+
47+
As you can see, in order to add or remove members to or from the team, the `Team` class is exposing a reference to it’s internal \_members array (through the members getter).
48+
49+
This clearly “breaks encapsulation” 😢
50+
51+
Users of the `Team` class:
52+
53+
1. Need to know about the class’s internal state (eg: the fact that it’s an array),
54+
55+
2. Can change the class’s internal state directly from the outside.
56+
57+
Let’s see how we can fix this.
58+
59+
## Enter the “Encapsulate Collection” refactoring
60+
61+
The [Encapsulate Collection refactoring](https://refactoring.com/catalog/encapsulateCollection.html) helps with this very exact issue. We can hide the internal \_members collection by adding a couple of new methods to manipulate it:
62+
63+
```js
64+
class Team {
65+
constructor(members) {
66+
// We store a "copy" of the collection
67+
this.\_members = members.slice();
68+
}
69+
70+
addMember(member) {
71+
return this.\_members.push(peter);
72+
}
73+
74+
removeMember(member) {
75+
const index = this.\_members.indexOf(member);
76+
return this.\_members.splice(index, 1);
77+
}
78+
79+
set members(members) {
80+
this.\_members = members.slice();
81+
}
82+
}
83+
```
84+
85+
Now, in order to add, remove, or replace the list of members, users of the Team class no longer need to know about the internal \_members collection:
86+
87+
```js
88+
// Add a member
89+
team.addMember(peter);
90+
91+
// Remove a member
92+
team.removeMember(peter);
93+
94+
// Replace the members
95+
team.members = [mike, john, peter];
96+
```
97+
98+
Isn’t it great?
99+
100+
Well, there’re a couple of issues… we still need / want to:
101+
102+
1. know the number of members of a team, and
103+
104+
2. iterate through them.
105+
106+
This is the code that we wish we had:
107+
108+
```js
109+
// Get the number of members
110+
console.log(team.length);
111+
112+
// Iterate through the members
113+
for (let member of team) {
114+
console.log(member.fullname);
115+
}
116+
```
117+
118+
As you can imagine, this doesn’t work:
119+
120+
- `Team` doesn’t have a length property or getter 😢.
121+
122+
- `Team` is not iterable 😭.
123+
124+
Can we make it work?
125+
126+
## Making the collection iterable
127+
128+
> TypeError: Team is not a function or its return value is not iterable
129+
130+
This 👆 is the error you’d get if you try to iterate through the team using for…of.
131+
132+
Luckily for us, the iterable protocol allows us to make objects iterable:
133+
134+
> In order to be iterable, an object must implement the @@iterator method, meaning that the object (or one of the objects up its prototype chain) must have a property with a @@iterator key which is available via constant Symbol.iterator
135+
136+
Looks promising, let’s give it a try:
137+
138+
```js
139+
class Team {
140+
// Rest of the class
141+
142+
[Symbol.iterator]() {
143+
return this.\_people[Symbol.iterator]();
144+
}
145+
}
146+
```
147+
148+
It works! 👏
149+
150+
We can now iterate through the collection like this:
151+
152+
```js
153+
for (let member of team) {
154+
console.log(member.fullname);
155+
}
156+
```
157+
158+
We could have defined our own implementation of the iteration behaviour, but in this case, we can just use Array‘s iterator directly 🙌.
159+
160+
## Exposing the length of the collection
161+
162+
`team.length` currently returns `undefined` because `Team` doesn’t have a property or getter with that name.
163+
164+
We can solve this easily by implementing a new length getter:
165+
166+
```js
167+
class Team {
168+
// Rest of the class
169+
170+
get length() {
171+
return this.\_members.length;
172+
}
173+
}
174+
```
175+
176+
## Exposing other useful methods
177+
178+
You might be wondering… This is pretty cool, but what if I want to use any of the other methods provided by Array? Something as useful as Array.prototype.includes()?
179+
180+
Well, we can use delegation to forward the request to the internal collection:
181+
182+
```js
183+
class Team {
184+
// Rest of the class
185+
186+
includes(person) {
187+
return this.\_people.includes(person);
188+
}
189+
}
190+
```
191+
192+
## Wrap up
193+
194+
By encapsulating the collection of members inside the Team class, we’re preventing unwanted modifications of the collection from the outside, and providing a nice interface to interact with the collection.
195+
196+
This can be easily done by:
197+
198+
1. Defining getters and methods to manipulate or access the collection.
199+
2. Provide an Array like experience by implementing a length getter and the Symbol.iterator method.
200+
201+
Here’s a link to the before and after versions of the code:
202+
203+
[https://gist.github.com/alonsogarciapablo/110b92c3d19f0074dd416286cae1a036](https://gist.github.com/alonsogarciapablo/110b92c3d19f0074dd416286cae1a036)

0 commit comments

Comments
 (0)