While we write code for web applications we are constantly trying to make good decisions. It's not always an easy task especially when our code becomes larger over time.
Fortunately there are some techniques we can incorporate into our code to solve complex issues. They're called design patterns.
This post will go over several source codes in the JavaScript world that you can look at to find some inspiration, answers, or even as a learning experience so that you can speed up your pace in learning the advanced coding techniques without feeling alone.
I noticed that not many articles out there directly reveal the patterns that are used in source codes and leave that task to the audience. I don't know about you but when I was new to programming this would have been very helpful. Don't worry, I got you covered.
Builder Design Pattern
One of my favorite libraries demonstrating the builder pattern in practice is spotify-web-api-node.
The builder design pattern is a behavior pattern that helps to construct objects that are otherwise complex without it.
This library constructs a builder that makes up a good majority of its implementation. For example most of its methods construct requests using one builder that reads like english:
Envision this without the builder providing this interface and you will see the benefit that the builder does for you.
Chaining / Fluent interfaces
We have actually just seen this technique in the last example, but we can also talk about jQuery that takes advantage of chaining methods together resulting in an easy to read fluent api to work with.
We are talking about a library that took the JavaScript community by storm before modern frameworks like React took their way into the scene, so this pattern is proven to be useful in programming.
jQuery was so popular back in the day that front end job listings preferred candidates with experience in jQuery. Although it isn't as popular as before, it is still being used by plenty of companies today.
cheerio is a library I still use today that was heavily inspired by the jQuery library, and remains popular today when topics like web scraping come up. It uses chaining to manipulate DOM nodes similarly to jQuery.
The moral of the story? It works.
Life cycles
As you start building more projects there will be a moment in time where you need to integrate some type of life cycle pipeline to ensure that functions are being processed in the correct time of events.
When consumed this can be useful to functions outside that need to tap into specific timing of events like manipulating DOM nodes after they are done applying their style attributes.
A good repository to learn from this concept is snabbdom, a virtual DOM library that focuses on simplicity, modularity, and powerful features to improve performance when working with the DOM.
They provide an extensible module api that allow developers to create their own modules to attach onto the main patch
function. The core implementation of each module is to tap into these life cycles which is what makes this library work in the way that it does for our web applications.
For example they provide an optional event listeners module that hooks into this life cycle and ensures that event handlers are correctly attached and cleaned up between each patch (in other words each "rerender").
Command Design Pattern
Like jQuery, redux also soared in popularity but mostly within applications that needed to manage state which was basically every react app. It is by far my favorite example of the command pattern used in practice.
The pattern is facilitated through the concept of dispatching actions where each action is the command. Their documentation exclusively mentions that the only way to change its state is by dispatching actions.
The benefits this pattern provides are the main reasons it was popularized in react. Redux takes advantage of the command pattern by separating the objects that invoke actions away from those that know what to when they are being invoked. This is a perfect combination when used in conjunction with react. React is mostly about composition and separation of concerns between dumb and smart components. (However there are still different ways to architect react apps that don't utilize the concept of smart and dumb components).
Powerful middlewares were created to squeeze the most of the pattern's advantages such as being able to time travel in the redux devtools extension.
Modularity
When I first landed my eyes on the lodash repository to examine how their functions were structured, there were times I asked my self "What is the point of this function being here?" because functions like flowRight import another function just to call the function and return the result.
But over time as I started gaining more hands on experience I realized the beauty in structuring our modules/utility functions this way.
It helps you to think in the concept of reusability, functions with a single responsibility, and DRY (Do Not Repeat Yourself) when you write code. The benefit I take away from flowRight
structured in the way that it is is that by depending on flow
to do the "flow" logic, it only needs to be responsible for "flowing them to the right". Also, realize that if there updates in the impementation of flow
, it automatically reflects in flowRight
as well as all other functions that import flow
.
Abstract Syntax Trees and the Composite Design Pattern
I'll be honest, my approach to getting used to working with ASTs is a bit weird, but it worked for me. For some reason the thought of working with the TypeScript AST sounds really attractive to me. I'm sure most people recommend to start deep diving into babel first before getting used to working with an AST with the TypeScript compiler, but I started it the other way around. There is a great library called ts-morph that focuses on making it easier for developers to work with the TypeScript compiler. Learning hands on with ts-morph while getting used to their compiler api made babel much easier to understand without ever touching babel.
You will also notice that a lot of objects you work with share a similar interface. This is their interface exposed to consumers that uses the composite design pattern.
Proxy Design Pattern
The Proxy pattern provides a placeholder object to act as the real object. It controls access to the real object.
immer uses this pattern by returning to us a draft that represents the object you give to the produce
function. What it gets from this pattern is immutability which is great for react apps.
Observer / PubSub Design Pattern
One library that uses this pattern extensively is twilio-video.js. Almost every object ultimately extends the EventEmitter
whether by directly extending it or by inheritance.
Their core objects like Participant implements this pattern extensively which enable consumers of the api to create an event driven video chat experiences in their apps.
For example to observe when a users' (or participant) media tracks are ready (these are what gets attached to the DOM and start the streaming) you would observe their remote participant object in code via someParticipant.on('trackSubscribed', () => {...})
and handle it accordingly.
Chain of Responsibility Design Pattern
Implementing chain of responsibility in JavaScript usually involves a pipeline of loosely coupled functions where one or more can handle the request.
The best example demonstrating this pattern is the expressjs library through the concept of route handling.
For example, if you create a route handler for the route /dogs
and one for /dogs?id=3
and a user has navigated to /dogs?id=3
, there will be a chain of handlers invoking where /dogs
will get called first and can decide to handle this request or pass it on to the second handler that will get to decide from there, and so on.
Visitor Design Pattern
You will rarely see this pattern implemented in practice until you start digging deeper into tools. The visitor pattern is useful in cases where you want to work with each object in ASTs by "visiting" each AST node.
Visitors are used for many reasons like extensibility, plugins, printing an entire schema somewhere, etc.
Here is an implementation of one from the graphql repository
Prototype Design Pattern
The Prototype pattern's main concern is to ensure that objects being created are not new instances each time. This means if we create an object MathAdd
with a method add
, we should just reuse add
when we created multiple instances of MathAdd
since the implementation doesn't change. This is a performance benefit as well.
The request library uses this pattern on nearly all of their class objects.
Conclusion
And that concludes the end of this post! I hope you found this valuable and look out for more in the future!
Find me on medium
Top comments (0)