Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
Design patterns are the basis of any good software. JavaScript programs are no exception.
In this article, we’ll look at the factory and observer patterns.
Factory Pattern
The factory pattern is a design pattern that lets us create new objects and return them in a clean way.
For instance, we may want to return different kinds of things that are similar.
To make this easy for us, we can create a factory function that lets us return different kinds of objects with one factory function.
We can write:
class Fruit {
//...
}
class Apple extends Fruit {
//...
}
class Orange extends Fruit {
//...
}
const fruitFactory = (type) => {
if (type === 'apple') {
return new Apple()
} else if (type === 'orange') {
return new Apple()
}
}
We have the fruitFactory
factory function that gets the type
of an object as the argument and then return an object according to the type.
If we have 'apple'
as the value of type
, we return an Apple
instance.
And if we type
set to 'orange'
, we return an Orange
instance.
Now we can return different subclass instances of Fruit
without writing them out explicitly.
If we make any changes to the class or add or remove them, then we can still use the same factory function and don’t break anything.
Some factory functions in JavaScript are in the standard library.
They include String
for creating strings, Number
for creating numbers from other entities, Boolean
for convert variables to boolean, Array
for creating arrays, and more.
Now we don’t have to worry about changes to the class structure messing up our code since we used a factory function to create our Fruit
objects.
Observer
The observer pattern is where we have multiple objects that listen to the one observable object.
This way, the observable object, which is also called the subject, can notify the objects that subscribed to the observable object with data that are emitted.
For instance, we can write:
const observable = {
observers: {},
subscribe(obj) {
const id = Object.keys(this.observers).length + 1;
this.observers[id] = obj;
return id;
},
notify(data) {
for (const o of Object.keys(this.observers)) {
this.observers[o].listen(data);
}
}
}
const observer = {
listen(data) {
console.log(data);
}
}
observable.subscribe(observer);
observable.notify({
a: 1
});
We have the observable
object that lets observer objects, which have a listen
method to listen to data, to subscribe to notifications from the observable
object.
When we can observable.notify
with something as we did above, the listen
method of the observer objects is run.
This way, the communication is all done by communicating via the notify
method and nowhere else.
No implementations are exposed and therefore no tight coupling.
As long as the methods do the same thing, we shouldn’t have to worry about breaking the observer objects that subscribe to the observable
.
We can also add an unsubscribe
method to observable
which uses the returned id
from subscribe
to remove an observer object from the observers
object.
For instance, we can write:
const observable = {
observers: {},
subscribe(obj) {
const id = Object.keys(this.observers).length + 1;
this.observers[id] = obj;
return id;
},
notify(data) {
for (const o of Object.keys(this.observers)) {
this.observers[o].listen(data);
}
},
unsubscribe(id) {
delete this.observers[id];
}
}
const observer = {
listen(data) {
console.log(data);
}
}
const subscriberId = observable.subscribe(observer);
observable.notify({
a: 1
});
observable.unsubscribe(subscriberId);
We added an unsubscribe
method so that we can remove the observer object from the this.observers
list.
Once we unsubscribed, we won’t get notifications any more with the unsubscribed observer if we call notify
again.
Examples of the observer pattern are used in many places.
Another good thing about the observer pattern is that we don’t communicate anything through the classes.
Loose coupling should always be preferred to tight coupling.
We only communicate through one channel in the example above so the observer pattern is as loose as coupling can get.
They include message queues and event handlers for GUI events like mouse clicks and key presses.
Conclusion
We can use the observer pattern to decouple objects as much as possible.
All we do is receive events from one observable object that we want changes from.
The observable sends notifications to observer objects.
The factory pattern lets us create objects of similar types in one place by creating a factory function that lets us do that.