JavaScript composer

The translation of the article was prepared in anticipation of the start of the course “JavaScript Developer. Basic " .








In this article, we'll break down one of the structural design patterns in JavaScript - the linker . In software engineering, the linker allows you to refer to groups of objects as if they were separate objects, which ensures that the overall structure of these objects and their combinations is consistent.



The main task of a linker is to combine many objects into a single tree structure . This tree structure represents a hierarchy that is structured from the particular to the whole .



To better understand how the linker works, you need to understand how the hierarchy from the particular to the whole works and how it can be visualized.



In a hierarchy from particular to whole, each object in the collection is a part of the overall composition. This general composition, in turn, is a collection of its parts . The hierarchy from the particular to the whole is built as a tree-like structure, where each individual "leaf" or "node" is perceived and processed just like any other leaf or node in any part of the tree. So a group or collection of objects (a subtree of sheets / nodes) is also a leaf or a node.



Visually, it can be represented something like this:







Now that we have a clearer understanding of the relationship between the private and the whole, let's return to the term linker... We have defined that the use of the linker is to combine the mentioned objects (leaves / nodes) into a tree according to this principle.



Thus, we get a design pattern in which each element of the collection can also include other collections , which allows for deeply nested structures.



Internal structure



Each node in the tree shares a common set of properties and methods that enable it to maintain and interact with individual objects in the same way as with collections of objects. This interface assumes the construction of recursive algorithms that iterate over all objects in the composite collection.



Where does this pattern apply?



On operating systems, this pattern allows many possibilities, such as creating directories within directories.



Files (for convenience, all objects in a directory can be considered “elements” ) are leaves / nodes (parts) within a whole composite (directory). A subdirectory created in a directory is similarly a leaf or a node that includes other elements such as videos, images, etc. At the same time, directories and subdirectories are also composites , since they are collections of separate parts (objects, files, etc.).



Popular libraries like React or Vue use this pattern extensively to build reliable, reusable interfaces. All elements of web pages that you see are represented as components . Each component of a web page is a leaf of a tree, and it itself can combine many components (in this case, a composite is formed , but it is still a leaf of a tree ). It is a powerful tool that greatly simplifies development for library users. In addition, it allows you to create scalable applications that involve multiple objects.



Why is this template interesting?



In short: It's very powerful.



The linker is such a powerful design pattern because it allows an object to be treated as a composite by using a common interface for all objects.



This means you can reuse objects without fear of potential incompatibility with others.



When developing an application, you may need to work with objects that have a tree structure, in which case using this pattern can be very effective.



Examples of



Let's say we are developing an app for a company that helps doctors get certified for platforms that provide healthcare services remotely. The process includes collecting signatures for statutory documents.



We are going to work with a class Documentthat will have a property signaturewith a default value of false. If the doctor signs the document, the signature value will be changed to his signature. We also define a method in this class signthat implements this function.



This is what it will look like Document:



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




Now, using the linker, we will provide support for methods similar to those defined in Document.



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 the grace of the template becomes apparent. Pay attention to the last two code snippets: Let's take a look at the template visually:



Great! It looks like we are on the right track. What we got corresponds to the scheme presented above.







So, our tree structure contains two leaves / nodes - Documentand DocumentComposite. They both share the same interface and therefore act as “parts” of a single composite tree .



It should be noted here that a leaf / tree node that is not a composite ( Document) is not a collection or group of objects and therefore will not branch out further. However, the leaf / node beingcomposite, contains a collection of parts (in our case it is items). Also remember that Documentboth DocumentCompositeshare a common interface and therefore share the sign method.



So what is the effectiveness of this approach? Although DocumentComposite uses a single interface because it uses a sign method like Document, it takes a more efficient approach while still achieving its ultimate goal.



So instead of a structure like this:



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

const forms = []
forms.push(pr2Form)
forms.push(w2Form)

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


We can modify the code and make it more efficient by taking advantage of the linker:



const forms = new DocumentComposite()
const pr2Form = new Document(
  '     (PR2)',
)
const w2Form = new Document('   (W2)')
forms.add(pr2Form)
forms.add(w2Form)

forms.sign(' ')

console.log(forms)


With this approach, we only need to execute sign once after adding all the necessary documents, and this function will sign all documents.



You can verify this by looking at the output of the function console.log(forms):







In the previous example, we had to manually add documents to an array, and then independently iterate over each document and execute the function signto sign it.



Also, do not forget that ours DocumentCompositemay include a collection of documents.



So when we did this:



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


Our scheme has taken the following form:







We have added two forms, and now this scheme is almost completely identical to the original:





Nevertheless, our tree stops growing, since its last leaf has formed only two leaves, which does not quite correspond to the diagram in the last screenshot. If instead we made the w2form a composite, as shown here:



const forms = new DocumentComposite()
const pr2Form = new Document(
  '     (PR2)',
)
const w2Form = new DocumentComposite('   (W2)')
forms.add(pr2Form)
forms.add(w2Form)

forms.sign(' ')

console.log(forms)


Then our tree could continue to grow:





Ultimately, we would have achieved the same goal - all documents would have been signed:





This is where the linker comes in.



Conclusion



That's all for now! I hope this information was helpful to you. Further more!



Find me on medium







Read more:






All Articles