Decorator Pattern
What are they?
A decorator is a function that can be used to extend (decorate) the functionality of a certain object at run-time, independently of other instances of the same class.
- This is achieved by designing a new Decorator class that wraps the original class/method.
- One value is that they read more like plain English, and allow us to discern at a distance what behaviour has been added to a given class/method.
Decorators are helpful for anything you want to transparently wrap with extra functionality.
- ex. memoization, enforcing access control and authentication, instrumentation and timing functions, logging, rate-limiting etc.
Decorators help us adhere to Single Responsibility Principle, since it allows functionality to be divided between classes with unique areas of concern.
- We might have solved this problem with subclassing, but decorators can be more efficient because an object's behavior can be augmented without defining an entirely new object.
- subclassing (ie.
extend
ing) happens at compile time, meaning that this binding cannot be changed at run-time.
- subclassing (ie.
Differences between other structural patterns
While an Adapter provides a different interface to its subject, and a Proxy provides the same interface, a Decorator provides an enhanced interface.
- a Decorator enhances an object's responsibilities. Decorator is thus more transparent to the client. As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters.
An adapter should be used when the wrapper must respect a particular interface and must support polymorphic behavior.
Decorator and Proxy have different purposes but similar structures. Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests.
How are they used?
Decorators can be thought of as functions.
- you can think of:
as@Decorator class Foo {}
Decorator( new Foo() )
Imagine having a Decorator
class that implements the interface of the extended (decorated) object transparently by forwarding all requests to it
Example
Imagine that we had a @UserValidatorDecorator
whose sole responsibility was to verify that a username adheres to our application's business logic:
- note: not valid code; just for illustrative purposes
function UserValidatorDecorator(user) {
if (user.username === "") {
throw new Error('Invalid!')
}
}
Then, we can use the decorator to ensure that each time we instantiate, the logic of the decorator is used:
@UserValidatorDecorator
class User {
username: string = ""
}
Decorator Factory
A Decorator Factory is simply a function that returns the expression that will be called by the decorator at runtime.
Example:
function color(value: string) {
// this is the decorator factory. it sets up the returned decorator function
return function (target) {
// this is the decorator. do something with 'target' and 'value'...
};
}
Alternatives to Decorators
Backlinks