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.
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.
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:
PageComponent
class that acts as a Component
Page
class that acts as a ConcreteComponent
PaginationDecorator
class 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()
Lines 2–6: We created an interface class PageComponent
with a display
method.
Lines 9–19: We created a simple class Page
that extends the PageComponent
. It has a constructor that initializes the page with content and a display
method to print the content of the page to the console.
Lines 22–51: We created a decorator class PaginationDecorator
that extends the PageComponent
and wraps the instance of the Page
class and adds the pagination functionality. The PaginationDecorator
class constructor accepts a Page
object and a pageSize
to determine the amount of content displayed per page. This class has three methods: display()
, nextPage()
and previousPage()
. The display()
method shows the current page, nextPage()
method is to move to the next page to show its content, and previousPage()
method is to go back to the previous page to show its content.
Lines 54–67: We created an instance of Page
class, added the content, and wrapped this instance with the PaginationDecorator
decorator, specifying a page size of 50 characters. We then used the display()
method to show the current page’s content, the nextPage()
method to display the following page, and the previousPage()
method to display the previous page on the console.
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.
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