Going over the Composite Design Pattern in JavaScript

Going over the Composite Design Pattern in JavaScript

In software engineering, the composite pattern is a pattern where a group of objects is to be treated in the same way as a single instance of a single object, resulting in uniformity with these objects and compositions.

In this post, we will be going over the composite design pattern in JavaScript.

In software engineering, the composite pattern is a pattern where a group of objects is to be treated in the same way as a single instance of a single object, resulting in uniformity with these objects and compositions.

The intention of a composite is to compose multiple objects into a certain tree structure. This tree structure represents a part-whole hierarchy.

To understand the composite pattern in greater detail, we’d have to understand what a part-whole is and what it would look like from a visual perspective.

In terms, a part-whole relationship is basically where each object in a collection is a part of the whole composition. This whole composition is a collection of parts.

Now, when we think of a part-whole hierarchy, it’s a tree structure where each _individual “_leaf” or “node” is treated the same as every other leaf or node in the tree. This means that a group or collection of objects (sub-tree of leaves/nodes) is also a leaf or node.

From a visual perspective, an example of that can end up looking something like this:

Composite Design Pattern

Now that we have a clearer understanding of the part-whole concept, let’s go back to the term composite. We said that the intention of a composite is to compose any of these objects (leaves/nodes) into a tree, representing this concept.

And so, the composite design pattern is where each item in a collection can hold other collections themselves, enabling them to create deeply nested structures.

The Anatomy

Every node in the tree structure shares a common set of properties and methods which enables them to support individual objects and treat them the same as a collection of objects.

This interface promotes the construction and design of algorithms that are recursive and iterate over each object in the composite collection.

Who Uses the Pattern?

Operating systems use the pattern which, in turn, leads to useful features like allowing us to create directories inside other directories.

The files (we can refer to anything inside a directory as an “item” at this point, which makes more sense) are the leaves/nodes (parts) of the whole composite (the directory).

Creating a sub-directory in this directory is also a leaf/node including other items like videos, images, etc. However, a directory or sub-directory is also a composite because it’s also a collection of parts (objects/files/etc.).

Popular libraries like React and Vue make extensive use of the composite pattern to build robust, reusable interfaces. Everything you see on a web page is represented as a component.

Each component of the web page is a leaf of the tree and can compose multiple components together to create a new leaf. (When this happens, it’s a composite but it is still a leaf of the tree).

This is a powerful concept as it helps make development much easier for consumers of the library, in addition to making it highly convenient to build scalable applications that utilize many objects.

Why Should We Care About This Pattern?

The easiest way to put it: Because it’s powerful.

What makes the composite design pattern so powerful is its ability to treat an object as a composite object. This is possible because they all share a common interface.

What this means is that you can reuse objects without worrying about incompatibility with others.

When you’re developing an application and you come across a situation where you’re dealing with objects that have a tree structure, it could end up being a very good decision to adopt this pattern into your code.

Examples

Let’s say we are building an application for a new business where its main purpose is to help doctors qualify for telemedicine platforms. They do this by collecting their signatures for mandatory documents that are required by law.

We’re going to have a Document class that will have a signature property with a default value of false. If the doctor signs the document, signature should flip its value to their signature.

We're also defining a sign method onto it to help make this functionality happen.

This is what the Document will look like:

Document.ts

class Document {
  constructor(title) {
    this.title = title
    this.signature = null
  }
  sign(signature) {
    this.signature = signature
  }
}

Now, when we implement the composite pattern, we’re going to support similar methods that a Document defined.

DocumentComposite.js

class DocumentComposite {
  constructor(title) {
    this.items = []
    if (title) {
      this.items.push(new Document(title))
    }
  }
  add(item) {
    this.items.push(item)
  }
  sign(signature) {
    this.items.forEach((doc) => {
      doc.sign(signature)
    })
  }
}

Now, here comes the beauty of the pattern. Pay attention to our two most recent code snippets. Let’s see this from a visual perspective:

Composite Design Pattern

Great! It seems like we are on the right track. We know this because what we have resembles the diagram we had before:

Composite Design Pattern

So, our tree structure contains two leaves/nodes, the Document, and the DocumentComposite. They both share the same interface so they both act as “parts” of the whole composite tree.

The thing here is that a leaf/node of the tree that is not a composite (the Document) is not a collection or group of objects, so it will stop there.

However, a leaf/node that is a composite holds a collection of parts (in our case, the items). And remember, the Document and DocumentComposite share an interface, sharing the sign method.

So, where’s the power in this?

Well, even though the DocumentComposite shares the same interface because it has a sign method just like the Document does, it is actually implementing a more robust approach while still maintaining the end goal.

So, instead of this:

forms.js

const pr2Form = new Document(
  'Primary Treating Physicians Progress Report (PR2)',
)

const w2Form = new Document('Internal Revenue Service Tax Form (W2)')

const forms = []

forms.push(pr2Form)
forms.push(w2Form)

forms.forEach((form) => {
  form.sign('Bobby Lopez')
})

We can change our code to make it more robust, taking advantage of the composite:

forms.js

const forms = new DocumentComposite()

const pr2Form = new Document(
  'Primary Treating Physicians Progress Report (PR2)',
)

const w2Form = new Document('Internal Revenue Service Tax Form (W2)')

forms.add(pr2Form)
forms.add(w2Form)

forms.sign('Bobby Lopez')

console.log(forms)

In the composite approach, we only need to sign once after we added the documents we needed, and it signs all of the documents.

We can confirm this by looking at the result of console.log(forms):

Composite Design Pattern

In the example prior to this, we had to manually add the items to an array, loop through each document ourselves, and sign them.

Let’s also not forget the fact that our DocumentComposite can hold a collection of items.

So, when we did this:

forms.add(pr2Form) // Document
forms.add(w2Form) // Document

Our diagram turned into this:

Composite Design Pattern

This closely resembles our original diagram as we added the two forms:

Composite Design Pattern

However, our tree stops because the last leaf of the tree rendered two leaves only, which isn’t exactly the same as this last screenshot. If we instead made w2form a composite, like this:

forms.js

const forms = new DocumentComposite()

const pr2Form = new Document(
  'Primary Treating Physicians Progress Report (PR2)',
)

const w2Form = new DocumentComposite('Internal Revenue Service Tax Form (W2)')

forms.add(pr2Form)
forms.add(w2Form)

forms.sign('Bobby Lopez')

console.log(forms)

Then our tree can continue to grow:

Composite Design Pattern

And, in the end, we still achieved the same goal where we needed our mandatory documents to be signed:

Composite Design Pattern

And that is the power of the composite pattern.

Conclusion

And that concludes this post! I hope you found this to be valuable and look out for more in the future!

javascript programming

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Learning JavaScript: Development Environments for JavaScript Programming

One of the nice things about learning JavaScript these days is that there is a plethora of choices for writing and running JavaScript code. In this article, I’m going to describe a few of these environments and show you the environment I’ll be using in this series of articles.

Learning JavaScript: Data Types and Variables

To paraphrase the title of an old computer science textbook, “Algorithms + Data = Programs.” The first step in learning a programming language such as JavaScript is to learn what types of data the language can work with. The second step is to learn how to store that data in variables. In this article I’ll discuss the different types of data you can work with in a JavaScript program and how to create and use variables to store and manipulate that data.

[ Professor JavaScript ]: Introduction

Professor JavaScript is a JavaScript online learning courses YouTube Channel. Students can learn how to develop codes with JavaScript from basic to advanced levels through the online courses in this YouTube channel.

From imperative to declarative JavaScript

In this post, I will explain why declarative code is better than imperative code. Then I will list some techniques to convert imperative JavaScript to a declarative one in common situations.

JavaScript Memory Management System

The main goal of this article is help to readers to understand that how memory management system performs in JavaScript. I will use a shorthand such as GC which means Garbage Collection. When the browsers use Javascript, they need any memory location to store objects, functions, and all other things. Let’s deep in dive that how things going to work in GC.