The Power of the Composite Design Pattern in JavaScript

The Power of the Composite Design Pattern in JavaScript

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.

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 intentions of a composite is to compose multiple objects into a certain tree structure. This tree structure represents a part-whole hierarchy.

In order to understand the composite pattern in greater detail we'd have to understand what a part-whole is and what it would look like in 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 leafs/nodes) is also a leaf or node.

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

Now that we have a clearer understanding of the part-whole concept, lets go back to the term composite. We said that the intentions of a composite is to compose any of these objects (leafs/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 is using the pattern?

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

The files (we can refer to anything inside a directory an "item" at this point which makes more sense) are the leafs/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 in 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 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 your 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

Lets 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 how the Document will look like:

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 has defined.

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 in a visual perspective:

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

So our tree structure contains 2 leafs/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 shares 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:

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:

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):

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:

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

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

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:

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

And that, is the power of the composite pattern.

Conclusion

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

JavaScript Tutorial: if-else Statement in JavaScript

JavaScript Tutorial: if-else Statement in JavaScript

This JavaScript tutorial is a step by step guide on JavaScript If Else Statements. Learn how to use If Else in javascript and also JavaScript If Else Statements. if-else Statement in JavaScript. JavaScript's conditional statements: if; if-else; nested-if; if-else-if. These statements allow you to control the flow of your program's execution based upon conditions known only during run time.

Decision Making in programming is similar to decision making in real life. In programming also we face some situations where we want a certain block of code to be executed when some condition is fulfilled.
A programming language uses control statements to control the flow of execution of the program based on certain conditions. These are used to cause the flow of execution to advance and branch based on changes to the state of a program.

JavaScript’s conditional statements:

  • if
  • if-else
  • nested-if
  • if-else-if

These statements allow you to control the flow of your program’s execution based upon conditions known only during run time.

  • if: if statement is the most simple decision making statement. It is used to decide whether a certain statement or block of statements will be executed or not i.e if a certain condition is true then a block of statement is executed otherwise not.
    Syntax:
if(condition) 
{
   // Statements to execute if
   // condition is true
}

Here, condition after evaluation will be either true or false. if statement accepts boolean values – if the value is true then it will execute the block of statements under it.
If we do not provide the curly braces ‘{‘ and ‘}’ after if( condition ) then by default if statement will consider the immediate one statement to be inside its block. For example,

if(condition)
   statement1;
   statement2;

// Here if the condition is true, if block 
// will consider only statement1 to be inside 
// its block.

Flow chart:

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If statement 

var i = 10; 

if (i > 15) 
document.write("10 is less than 15"); 

// This statement will be executed 
// as if considers one statement by default 
document.write("I am Not in if"); 

< /script> 

Output:

I am Not in if
  • if-else: The if statement alone tells us that if a condition is true it will execute a block of statements and if the condition is false it won’t. But what if we want to do something else if the condition is false. Here comes the else statement. We can use the else statement with if statement to execute a block of code when the condition is false.
    Syntax:
if (condition)
{
    // Executes this block if
    // condition is true
}
else
{
    // Executes this block if
    // condition is false
}


Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If-else statement 

var i = 10; 

if (i < 15) 
document.write("10 is less than 15"); 
else
document.write("I am Not in if"); 

< /script> 

Output:

i is smaller than 15
  • nested-if A nested if is an if statement that is the target of another if or else. Nested if statements means an if statement inside an if statement. Yes, JavaScript allows us to nest if statements within if statements. i.e, we can place an if statement inside another if statement.
    Syntax:
if (condition1) 
{
   // Executes when condition1 is true
   if (condition2) 
   {
      // Executes when condition2 is true
   }
}

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate nested-if statement 

var i = 10; 

if (i == 10) { 

// First if statement 
if (i < 15) 
	document.write("i is smaller than 15"); 

// Nested - if statement 
// Will only be executed if statement above 
// it is true 
if (i < 12) 
	document.write("i is smaller than 12 too"); 
else
	document.write("i is greater than 15"); 
} 
< /script> 

Output:

i is smaller than 15
i is smaller than 12 too
  • if-else-if ladder Here, a user can decide among multiple options.The if statements are executed from the top down. As soon as one of the conditions controlling the if is true, the statement associated with that if is executed, and the rest of the ladder is bypassed. If none of the conditions is true, then the final else statement will be executed.
if (condition)
    statement;
else if (condition)
    statement;
.
.
else
    statement;


Example:

<script type = "text/javaScript"> 
// JavaScript program to illustrate nested-if statement 

var i = 20; 

if (i == 10) 
document.wrte("i is 10"); 
else if (i == 15) 
document.wrte("i is 15"); 
else if (i == 20) 
document.wrte("i is 20"); 
else
document.wrte("i is not present"); 
< /script> 

Output:

i is 20

App Designing Rules

App Designing Rules

In this highly competitive world of apps, the designers need to up their game in delivering products that work. And, here are the rules that help in ensuring just that for them. Learn More at #Way2Smile - [**App Designing Company in...

In this highly competitive world of apps, the designers need to up their game in delivering products that work. And, here are the rules that help in ensuring just that for them.

Learn More at #Way2Smile - App Designing Company in Chennai

#AppDesigningRules #AppDesigning #MobileAppDesign #UIDesign #UXDesign

Understanding Design Patterns in JavaScript - Mediator Pattern

Understanding Design Patterns in JavaScript - Mediator Pattern

In this JavaScript Design Patterns tutorial, we'll learn What the Mediator Pattern is? How can we use the Mediator Pattern to build a chat room? What are design patterns? What are JavaScript design patterns? Learning JavaScript Design Patterns

Understanding Design Patterns in JavaScript - Mediator Pattern

What is the Mediator Pattern? How can we use it to build a chat room?

/*
    Mediator Design Pattern -> https://youtu.be/ZuhgOu-DGA4
    Author: DevSage (Youtube) -> https://www.youtube.com/DevSage
*/


function Member(name)
{
  this.name = name
  this.chatroom = null
}

Member.prototype = {
  send: function(message, toMember)
  {
    this.chatroom.send(message, this, toMember)
  },
  receive: function(message, fromMember)
  {
    console.log(`${fromMember.name} to ${this.name}: ${message}`)
  }
}

function Chatroom()
{
  this.members = {}
}

Chatroom.prototype = {
  addMember: function(member)
  {
    this.members[member.name] = member
    member.chatroom = this
  },
  send: function(message, fromMember, toMember)
  {
    toMember.receive(message, fromMember)
  }
}

const chat = new Chatroom()

const bob = new Member("Bob")
const john = new Member("John")
const tim = new Member("Tim")

chat.addMember(bob)
chat.addMember(john)
chat.addMember(tim)

bob.send("Hey, John", john)
john.send("What's up, Bob", bob)
tim.send("John, are you ok?", john)