Introduction
The Decorator Pattern is a structural design pattern that allows you to add new functionality to an object without modifying its original code. Instead of changing an existing object or class, you create a wrapper (called a decorator) that extends its behavior.
In JavaScript, the Decorator Pattern is commonly implemented using functions, higher-order functions, or object composition. This approach keeps code flexible, reusable, and easy to maintain.
The Decorator Pattern follows the Open/Closed Principle of software design:
Open for extension
Closed for modification
This means you can extend an object’s functionality without changing its original implementation.
In Node.js, decorators are useful for adding features such as logging, validation, authentication, caching, performance monitoring, and error handling.
For automation engineers, the Decorator Pattern can simplify browser actions, API requests, reporting, screenshot capture, logging, retry mechanisms, and test execution.
In this tutorial, you’ll learn the decorator concept and how it works in JavaScript.
What is the Decorator Pattern?
The Decorator Pattern allows you to wrap an existing function or object with another function that adds additional behavior.
Instead of modifying the original function, the decorator enhances it.
Why Use the Decorator Pattern?
The Decorator Pattern helps developers:
Extend existing functionality.
Avoid modifying original code.
Improve code reusability.
Follow the Open/Closed Principle.
Add features dynamically.
Keep code modular.
Improve maintainability.
Basic Structure
Original Function
↓
Decorator Function
↓
Enhanced Function
Example 1: Basic Decorator
function greet() {
console.log(
"Hello!"
);
}
function decorator(func) {
return function () {
console.log(
"Before greeting"
);
func();
console.log(
"After greeting"
);
};
}
const enhancedGreet =
decorator(greet);
enhancedGreet();
Sample Output
Before greeting
Hello!
After greeting
Example 2: Decorating a Calculator
function add(a, b) {
return a + b;
}
function logger(func) {
return function (a, b) {
console.log(
"Calculating..."
);
return func(a, b);
};
}
const decoratedAdd =
logger(add);
console.log(
decoratedAdd(10, 20)
);
Sample Output
Calculating...
30
Example 3: Timing a Function
function processData() {
console.log(
"Processing data..."
);
}
function timer(func) {
return function () {
console.time(
"Execution"
);
func();
console.timeEnd(
"Execution"
);
};
}
const decoratedProcess =
timer(processData);
decoratedProcess();
Sample Output
Processing data...
Execution: <execution time>
Example 4: Validation Decorator
function register(name) {
console.log(
"Registered:",
name
);
}
function validate(func) {
return function (name) {
if (!name) {
console.log(
"Name is required."
);
return;
}
func(name);
};
}
const registerUser =
validate(register);
registerUser("Rahul");
registerUser("");
Sample Output
Registered: Rahul
Name is required.
Example 5: Authentication Decorator
function dashboard() {
console.log(
"Dashboard opened."
);
}
function authenticate(func) {
return function () {
const loggedIn = true;
if (loggedIn) {
func();
}
else {
console.log(
"Access denied."
);
}
};
}
const secureDashboard =
authenticate(dashboard);
secureDashboard();
Sample Output
Dashboard opened.
Automation Testing Examples
Decorators are useful for adding reusable functionality to automation scripts.
Playwright Example
Add logging before browser launch.
function launchBrowser() {
console.log(
"Browser launched."
);
}
function logger(func) {
return function () {
console.log(
"Launching browser..."
);
func();
};
}
const browser =
logger(launchBrowser);
browser();
Sample Output
Launching browser...
Browser launched.
Selenium Example
Add retry information.
function executeTest() {
console.log(
"Test executed."
);
}
function retry(func) {
return function () {
console.log(
"Retry enabled."
);
func();
};
}
const test =
retry(executeTest);
test();
Sample Output
Retry enabled.
Test executed.
Cypress Example
Capture a screenshot after execution.
function verifyPage() {
console.log(
"Page verified."
);
}
function screenshot(func) {
return function () {
func();
console.log(
"Screenshot captured."
);
};
}
const page =
screenshot(verifyPage);
page();
Sample Output
Page verified.
Screenshot captured.
API Testing Example
Log API execution.
function callApi() {
console.log(
"API called."
);
}
const api =
logger(callApi);
api();
Sample Output
Launching browser...
API called.
Data-Driven Testing Example
Validate test data before execution.
function execute(data) {
console.log(
"Executing:",
data
);
}
const runTest =
validate(execute);
runTest("TC001");
runTest("");
Sample Output
Executing: TC001
Name is required.
Real-World Uses of the Decorator Pattern
The Decorator Pattern is commonly used for:
Logging.
Authentication.
Authorization.
Validation.
Retry mechanisms.
Performance monitoring.
Error handling.
Caching.
Screenshot capture.
Test reporting.
Benefits of the Decorator Pattern
Promotes code reuse.
Keeps original code unchanged.
Makes functionality easy to extend.
Reduces duplicate code.
Improves maintainability.
Encourages modular design.
Supports flexible application architecture.
Common Mistakes
Modifying the Original Function
The decorator should enhance the original function without changing its implementation.
Adding Too Many Responsibilities
A decorator should perform one specific enhancement instead of handling multiple unrelated tasks.
Forgetting to Return the Wrapped Function
Decorator functions should return a new function that wraps the original behavior.
Best Practices
Keep decorators focused on one responsibility.
Do not modify the original function.
Use descriptive decorator names.
Chain decorators when multiple enhancements are needed.
Use decorators for reusable functionality.
Keep the wrapped code simple and readable.
Test decorators independently.
Conclusion
The Decorator Pattern is a powerful design pattern that allows developers to extend the behavior of objects and functions without modifying their original implementation. It promotes reusable, modular, and maintainable code while following the Open/Closed Principle.
For automation engineers, the Decorator Pattern is especially valuable for adding logging, retries, authentication, reporting, screenshots, and validation to automation scripts. By separating these concerns from the core business logic, automation code becomes cleaner, more reusable, and easier to maintain.
Frequently Asked Questions (FAQs)
What is the Decorator Pattern?
It is a structural design pattern that adds new functionality to an object or function without modifying its original code.
Why use the Decorator Pattern?
It allows developers to extend behavior while keeping the original implementation unchanged.
Does JavaScript support decorators?
JavaScript supports the Decorator Pattern using functions and object composition. (ECMAScript decorators are a separate language feature with evolving support.)
What is the biggest advantage of the Decorator Pattern?
It improves flexibility, reusability, and maintainability by separating additional behavior from core functionality.
Why is the Decorator Pattern useful in automation testing?
It helps add reusable features such as logging, retries, screenshots, validation, reporting, and authentication without changing the original test logic.
Key Takeaways
The Decorator Pattern extends functionality without modifying original code.
It follows the Open/Closed Principle.
Decorators wrap existing functions or objects.
They improve modularity and code reuse.
Decorators are commonly implemented using higher-order functions in JavaScript.
They are useful for logging, validation, authentication, and caching.
Automation frameworks benefit from decorators for retries, reporting, and screenshots.
Decorators should have a single responsibility.
Multiple decorators can be combined to add layered functionality.
Understanding the Decorator Pattern helps build flexible and maintainable Node.js applications.
