If you’re anything like me, after you spent considerable time chopping up codes for all types of applications, be it web or mobile app, you’ve already come across with code that shares some similar patterns as the codebase itself has grown over some significant portion of the time.
From these observations, we programmers developed our conversations on design patterns in making scalable software solutions.
In particular with JS, with the influx of JS libraries, frameworks, tools etc, we can build our applications to solve some particular problems in so many different ways. But, no matter how much tooling JS developers are going to be choosing, there’s no better substitute for incorporating useful patterns in your code design where you see fit.
Thus the question remains - what are the common and useful design patterns that modern JS developers will be dealing on a day to day basis?
At the very core of everything, after working with JS space for several years, you should learn and be aware of these:
- Constructor Pattern
- Prototype Pattern
- Module Design Pattern
- Observer Pattern
- Decorator Pattern
Constructor Patterns
This pattern is well-known for creating and initializing new objects when memory is allocated. Just like a traditional object-oriented purist, Javascript also shares the same object constructor blueprint such as Java or C#.
Thus, prior to ES6/7/8/9, we had the following traditional way to do things.
// Constructor Pattern - old school approach
/* Option 1 - using Object.create function call and object literal presentation */
var shirt = {
colour: "red"
size: "S"
price: 3.5
}
var blueshirt = Object.create(shirt)
blueshirt.colour = 'blue'
blueshirt.size = "M"
blueshirt.price = 5.5
/* Option 2 - use the 'new' keyword */
function Shirt(colour, size, price) {
this.colour = colour;
this.size = size;
this.price = price;
this.toString = function() {
return this.colour + " shirt has size " + this.size + ", selling for $" + this.price;
}
}
var blueshirt = new Shirt("blue", "M", 3.5)
var redshirt = new Shirt("red", "L", 5.5)
Back in my days of BackboneJS web development several years ago, this is the most common pattern approach when making object creations for your Models, Collections and Controllers etc. You notice this pattern is very apparent all over the place.
But since then, the world moved on, ES6 arrived and we now have the following way that achieves the same thing using new class
and constructor
approach.
// Constructor Pattern - ES6 approach
class Shirt {
constructor(colour, size, price) {
this.colour = colour;
this.size = size;
this.price = price;
}
toString() {
return `${this.colour} shirt has size ${this.size}, selling for $${this.miles}`
}
}
let blueShirt = new Shirt("blue", "M", 3.5)
let redShirt = new Shirt("red", "L", 5.5)
You’ll find that most modern JS frameworks that came out during post-ES6 era will embed this constructor patterns all over the place such as React, VueJS, AngularJS. You can google up “some_js_framework_name constructor example “, and you will see what I mean.
Prototype Pattern
Next, we have another variation of object creation patterns, but it makes use of JS prototypical inheritance whereby objects are created to prototypes for all object types. Prototypes act as blueprint for each object constructor created. Prior to ES6, It is normally done using prototype
bindings.
// Prototype Pattern - Old school approach
var Shirt = function(colour, size, price) {
this.colour = colour;
this.size = size;
this.price = price;
}
Shirt.prototype = {
changeColour: function (newColour){ this.colour = newColour;},
changeSize: function (newSize){ this.size = newSize;},
changePrice: function (newPrice){ this.price = newPrice;}
}
var blueShirt = new Shirt("blue", "M", 5.5)
blueShirt.changeColour("red");
blueShirt.changeSize("L");
blueShirt.changePrice(3.5);
console.log(blueShirt); //outputs - Shirt {colour: "red", size: "L", price: 3.5}
Its ES6 counterpart is the same as its constructor pattern example above.
// Prototype Pattern - ES6 way
class Shirt {
constructor(colour, size, price) {
this.colour = colour;
this.size = size;
this.price = price;
}
changeColour(newColour) {}
changeSize(newSize) {}
changePrice(newPrice) {}
toString() {
return `${this.colour} shirt has size ${this.size}, selling for $${this.miles}`
}
}
The reason for this is class
and constructor
are just plain syntactic sugars for prototype bindings as part of the internal mechanics. This option gives us the freedom to write cleaner code and confidence to write true object-oriented code thus developers coming from Java, C# etc will feel more at ease using these.
For these same reasons, you can now emulate object inheritance using extends
keyword as well
// classic OOP inheritance example
class Employee {
constructor(name, salary, tax_rate) {
this.name = name;
this.salary = salary;
this.tax_rate = tax_rate;
this.total_working_hours = 40
}
calculateSalary(){
// some calculations to perform here
}
}
class FullTimer extends Employee {
//properties to describe full-timer's role
}
class Contractor extends Employee {
//properties to describe contractor's role
}
Module Design Pattern
This pattern is used as an improvement to the prototype pattern approach. Different types of modifiers (both private and public) are set in the module pattern. You can create similar functions or properties without conflicts.
// Module Design Pattern - old school approach
var Shirt = (function() {
// private variables
var _colour = "blue";
var _size = "M";
var _price = 5.5;
// public methods and properties
return {
colour: _colour,
size: _size,
price: _price,
changeColour: function(newColour) {
this._colour = newColour;
},
changeSize: function(newSize) {
this._size = newSize;
},
changePrice: function(newPrice) {
this._price = newPrice;
}
}
})();
Notice, in the above, my private variable declarations are denoted with _
prefixes. This is more of a convention than an actual language feature implementation as JS does not, at the time of writing, have a way to present private variables/properties like you do in Java, C# etc. Just to add that for clarity.
Again, you can find similar patterns of these used in several jQuery-influenced JS frameworks such as BackboneJS etc.
After ES6 come along, we now use import/export
mechanism instead. Thus we get:
// Module Design Pattern - ES6 way
// Saved in a some-module-design-pattern-es6.js
class Shirt {
constructor(colour, size, price) {
this.colour = colour;
this.size = size;
this.price = price;
}
changeColour(newColour) {}
changeSize(newSize) {}
changePrice(newPrice) {}
toString() {
return `${this.colour} shirt has size ${this.size}, selling for $${this.miles}`
}
}
export default Shirt;
// usage
import Shirt from './some-module-design-pattern-es6';
let blueShirt = new Shirt("blue", "M", 5.5)
Observer Design Pattern
This pattern is very useful when objects need to communicate with other sets of objects simultaneously. This is particularly true where there is shared a shared data/state that gets changed/updated across domain objects that are subscribed to listen/respond for changes.
With this pattern, there is no unnecessary push and pull of events across the states, but rather the modules involved only modify the current state of data.
// Observer Pattern - old school approach
function Observer() {
this.observerList = [];
}
Observer.prototype = {
subscribe: function(element) {
this.observerList.push(element);
},
unsubscribe: function(element) {
var elementIndex = this.observerList.indexOf(element);
if(elementIndex > -1) {
this.observerList.splice(elementIndex, 1);
}
},
notifyAll: function() {
this.observerList.forEach(function(observerElement){
console.log("observerElement: " + observerElement.name + " has been notified";
})
},
}
Its ES6 equivalent:
// Observer Pattern - ES6 way
// again using class and constructor combo..
class Observer {
constructor() {
this.observerList = [];
},
subscribe(element) {
this.observerList.push(element);
}
unsubscribe(element) {
let elementIndex = this.observerList.indexOf(element);
if(elementIndex > -1) {
this.observerList.splice(elementIndex, 1);
}
}
notifyAll(element) {
this.observerList.forEach(function(observerElement){
console.log("observerElement: " + observerElement.name + " has been notified";
})
}
}
You will find tons of examples this observer pattern is used very heavily across all JS apps in any old JS frameworks you may find as I explained in my previous examples in this blog post.
Decorator Pattern
Lastly, this pattern aims to promote code re-use. They offer the ability to add additional behaviour or features to existing classes in a system dynamically.
It is common that you’d find applications (especially in the object-oriented world) that contain features requiring large quantity of distinct types of object it has to manage and deal with. Thus, keeping track for every object definition and creation would be monumental tasks to accomplish at given point of time during the running lifetime of an app.
// Decorator Pattern - old school approach
function Shirt(brandName) {
this.brandName = brandName;
this.colour = function() {return "blue";};
this.size = function() {return "M";};
this.price = function() {return 3;};
}
function swapColour(shirt) {
var c = shirt.colour();
shirt.colour = function() {
c = "blue";
return c;
}
}
function changeSize(shirt) {
var s = shirt.size();
shirt.colour = function() {
s = "S";
return s;
}
}
function addCostToPrice(shirt) {
var p = shirt.price();
shirt.price = function() {
return p + 1.50;
}
}
var shirt = new Shirt("Gucci");
swapColour(shirt);
changeSize(shirt);
addCostToPrice(shirt);
console.log(shirt.colour()); // blue
console.log(shirt.size()); // S
console.log(shirt.price()); // 4.50
Its ES6 equivalent would be:
// Decorator Pattern - ES6 approach (again using class and constructor combo)
class Shirt {
constructor(brandName) {
this.brandName = brandName;
this.colour = "blue";
this.size = "M";
this.price = 3;
}
}
function swapColour(shirt) {
shirt.colour = "white";
return shirt;
}
function changeSize(shirt) {
shirt.size = "L";
return shirt;
}
function addCostToPrice(shirt) {
shirt.price += 1.50;
return shirt;
}
const defaultShirt = new Shirt("Gucci")
const whiteShirt = swapColour(defaultShirt);
console.log(whiteShirt); // outputs - Shirt {brandName: "Gucci", colour: "white", size: "M", price: 3}
const whiteLargeShirt = changeSize(swapColour(defaultShirt));
console.log(whiteLargeShirt); // outputs - Shirt {brandName: "Gucci", colour: "white", size: "L", price: 3}
const priceyWhiteLargeShirt = addCostToPrice(changeSize(swapColour(defaultShirt)));
console.log(priceyWhiteLargeShirt); // outputs - Shirt {brandName: "Gucci", colour: "white", size: "L", price: 4.5}
With all the examples above, what you may realise is that these patterns share one thing in common - they’re come from the basis of object-oriented programming (OOP) design paradigm.
These are designed to revolve around object-oriented driven software systems. You can read up examples here(with jQuery) and there( with BackboneJS).
And recent years, we’ve been told to get educated in building applications using a different programming paradigm. And that is, functional programming (FP).
Without a doubt, we’re slowly starting to hear plenty of noise that functional developers/advocates are working on, thus languages like JS are no stranger to this concept.
Interestingly enough, Javascript treats functions as first-class citizen vs its object-oriented aspects.
Thus, modern frameworks such as React starting to appear, are embracing these fully.
The key questions are - what are common design patterns can you employ in the functional programming world, particularly with React?
Well.
There’s aplenty of them to describe here.
Examples of React patterns:
- Functional Components
- Class Components
- Presentational Components or Higher Order Components(HOC)
- Container Components
- Render Props Pattern
Functional Components
Function components are, as they plainly described, just simple functions that return components
Here’s an example.
// Functional Components
const Greeting = () => <div>Hello small world!</div>
// Functional Components with props
const Greeting = (props) => <div>Hello {props.name}}!</div>
What we’re saying here is that we’re not keeping states in the component so we always pass down the props to the functions so that they are reliably testable.
Class Components
It is said to be a class-based components because it has data and attributes that associate with the object’s state.
Stateful components are usually class-based components using thins like constructor properties etc.
// Class Based Component
import {Component} from "React";
class NumberRandomGenerator extends Component {
state = {number: Math.random()}
render() {
return (
<DataResult
result = {this.state.number}
title={'Your name is:' }
onClick={this.setstate({number: Math.random()})}
/>
)
}
}
Presentation Components or Higher Order Components (HOC)
Higher Order Components (HOC) is said to be design pattern, which is also know as a Decorator Pattern. Commonly, in ReactJS, a HOC is a component that wraps another component by adding extra functionality or extra properties. This allows abstraction from some commonly used logic and keeps your code DRY. It is how you distribute complex component structure between other components in ReactJS and a way to decouple your application logic and UI.
// Presentational Components or HOC Components
import {Component} from "React";
export const addSomeData = (WrappedComponent) => {
return class extends Component {
render() {
return <WrappedComponent number={this.state.number} name={this.state.name} {...this.props} />
}
}
}
export const withNumberAndName = addSomeData(DataResult)
Container Components
Container components’ sole responsibility is to have logic to set state or have functions to emit events up to a parent component. The general rule of the thumb is to keep your component as simple as possible with a Single Responsibility Principle design principle in mind, which essentially means your component must do one thing, but do it well.
Most often, these types of components are the HOCs that accommodate few presentational components.
// Container Components
import {Component} from "React";
class CommentListComponent extends Component {
state = {
comments: []
}
componentDidMount = async () => {
try {
const response = await fetch('https://someurl.com')
const result = await response.json();
this.setState({comments: result})
} catch(error) {
console.error('Error:', error);
}
}
render() {
return <CommentList comments={this.state.comments} />
}
}
Render Props Pattern
It is a technique or pattern used to share code between components using a prop whose value is a function .
They’re most helpful in sharing cross-cutting concerns thus allows you to share and re-use patterns and logic across components.
Here’s one example
// Render Props Pattern - based on the official docs - https://reactjs.org/docs/render-props.html
import {Component, Fragment} from "React";
import moment from "moment";
class Watch extends Component {
state = {
date: moment();
}
componentDidMount = () => (this.TICK = setInterval(this.update, 1000))
componentWillUnMount = () => clearInterval(this.TICK)
update = () => this.setState({date: moment()})
render = () => (
<div>
{this.props.render(this.state.date)}
</div>
)
}
const AnalogFace =({date}) => {
const seconds = (360 /60) * date.seconds();
const minutes = (360 /60) * date.minutes();
const hours = (360 /12) * date.format('h');
return (
<Fragment>
<span>{seconds}</span>
<span>{minutes}</span>
<span>{hours}</span>
</Fragment>
)
}
class App extends Component {
render = () => (
<Fragment>
<h1>Checkout this cool watch</h1>
<Watch render={date => <AnalogFace date={date} />} />
</Fragment>
)
}
Concluding thoughts
Now, with the examples I provided in this post, I would like to point out this is not about memorising these patterns by heart and always remembering to apply in all situations. Some patterns are good for something. Some may not. This is also, by far, not the most exhaustive list of design patterns you will ever gonna need. They are many more patterns out that still need yet to explore and get accustomed such as the ones you get from (in the OOP world) Gang of Four: Design Patterns: Elements of Reusable Object-oriented Software , or (in the FP world) you get several online academia reads or learning resources such as this Github link as an example.
I just sharing my years of experience in working within the JS space that I’ve been in both sides of the fence; one being with the OOP world, the other being with the FP world, and how there’s array of battle-tested software design patterns that developers and engineers have used over the years, regardless of the JS frameworks such React/Redux/VueJS/Angular etc you’ll be using.
The key takeaways from this is always be mindfully aware of the design patterns you will be seeing and using over and over again through your JS software development career. They come with several shapes and forms such that they will have a major influence on the present and future libraries/frameworks/tools you will come across in every software projects you’d be doing. What better way to get your hands dirty is to get very much down to the very basics of them by starting these.
From there, once you master them over time, you will evolve to get better and sharper in writing amazingly brilliant software as part of your craft! 🚀🚀🚀🚀🚀
Till then, Happy Coding!