What is decorator pattern in Node.js?
Design patterns are incredibly useful and one of the best programming practices followed by software developers. They provide solutions to those problems that are recurrent during software development. They are also known as GoF (Gang of Four) Design Patterns, and there are three software design patterns:
Creational
Behavioral
Structural
We’ll discuss one of the structural patterns called the decorator pattern.
Decorator pattern
The decorator pattern adds functionality to our classes without directly modifying or inheriting from them. Using this pattern, we can augment the behavior of an already existing object dynamically. Without affecting the other objects, the behavior is explicitly augmented to only those objects that we want to decorate.
The components that are typically used in the decorator pattern are the following:
Component: The interface and abstract class for the component.
Concrete component: The objects that implement the Component interface and that are being decorated.
Decorator: The abstract class that also acts as a base class for all the decorators.
Concrete decorator: The class that adds functionality to the objects.
Client: The part that interacts with the interface of the component.
The above illustration shows that the Component is the main interface class of the object functionalities. The ConcreteComponent implements the methods of the Component interface. The Decorator implements the Component interface, and it has a reference to the Component interface class, which allows the ConcreteDecorator to add more functionality to the object.
Let’s implement the pattern for better understanding.
Implementation of the decorator pattern
Consider a simple scenario in which we implement the decorator pattern to add a pagination functionality to a simple page class. We’ll implement the following components in Node.js:
PageComponentclass that acts as a ComponentPageclass that acts as a ConcreteComponentPaginationDecoratorclass that acts as a Decorator and ConcreteDecorator
// Componentclass PageComponent {display() {throw new Error("This method should be overridden");}}// ConcreteComponentclass Page extends PageComponent{constructor(content) {super();this.content = content}display() {console.log(this.content)}}// Decorator and ConcreteDecoratorclass PaginationDecorator extends PageComponent{constructor(page, pageSize) {super();this.page = pagethis.pageSize = pageSizethis.currentPage = 1}// Method to display pagesdisplay() {const start = (this.currentPage - 1) * this.pageSizeconst end = start + this.pageSizeconsole.log(this.page.content.substring(start, end))}// Method to show the next page contentnextPage() {this.currentPage++this.display()}// Method to show the previous page contentpreviousPage() {if (this.currentPage > 1) {this.currentPage--}this.display()}}// Create a new Page instance with some contentconst content = "Educative — Leading online learning platform made by developers, created for developers. Free Trial. Text-based courses with embedded coding environments help you learn without the fluff."const page = new Page(content)// Decorate the Page instance with paginationconst pagination = new PaginationDecorator(page, 50)// Display the first pagepagination.display()// Display the next pagepagination.nextPage()// Display the previous pagepagination.previousPage()
Explanation
Lines 2–6: We created an interface class
PageComponentwith adisplaymethod.Lines 9–19: We created a simple class
Pagethat extends thePageComponent. It has a constructor that initializes the page with content and adisplaymethod to print the content of the page to the console.Lines 22–51: We created a decorator class
PaginationDecoratorthat extends thePageComponentand wraps the instance of thePageclass and adds the pagination functionality. ThePaginationDecoratorclass constructor accepts aPageobject and apageSizeto determine the amount of content displayed per page. This class has three methods:display(),nextPage()andpreviousPage(). Thedisplay()method shows the current page,nextPage()method is to move to the next page to show its content, andpreviousPage()method is to go back to the previous page to show its content.Lines 54–67: We created an instance of
Pageclass, added the content, and wrapped this instance with thePaginationDecoratordecorator, specifying a page size of 50 characters. We then used thedisplay()method to show the current page’s content, thenextPage()method to display the following page, and thepreviousPage()method to display the previous page on the console.
Limitations
There are a few disadvantages of the decorator pattern that should be kept in mind while using the pattern:
It increases complexity as we create multiple classes for the decorators and components.
All the decorators and concrete components need to follow a single interface, which results in interface constraints.
Managing dependency between components and decorators sometimes becomes very tough if we work on large components.
Conclusion
The decorator pattern is particularly useful for scenarios where we need to build a flexible design, as it allows us to add objects dynamically. Its proper use, while keeping in mind its limitations, makes it a powerful tool in our Node.js applications.
Free Resources