Decorators are a structural design pattern that aim to promote code re-use. Similar to mixins, they can be considered another viable alternative to object sub-classing.
We use decorator pattern to add new behaviour to an object.
The decorator pattern isn't heavily tied to how objects are created by instead focuses on the problem of extending their functionality. Rather than just relying on prototypal inheritance, we work with a single base object and progressibely add decorator objects which provide the additional capabilites.
Attach Additional Responsibilites to an Object Dynamically. Decorators Provide a Flexible alternative to Sub Classing for extending functionality. - GoF
IMO, mixins are primarily used to add new functionalities & decorators are used to modify existing functionalities.
The intent of a decorator is simple : Decorators are meant to add behavior to the object they wrap.
java.io package is largely based on Decorator. A typical code using it could look someting like this:
inputStrem = new ZipInputStream(
new BufferedInputStrem (
new FileInputStream("test.txt")
)
)
class SimpleStream extends Stream {
constructor(stream = null) {
super();
this.stream = stream;
this.data = '[simple data]'
}
}
class CompressedStream extends Stream {
constructor(stream = null) {
super();
this.stream = stream;
this.data = '[compressed' + stream.data + ']';
}
}
class EncryptedStream extends Stream {
constructor(stream = null) {
super();
this.stream = stream;
this.data = '[encrypted' + stream.data + ']';
}
}
// Usage:
let simpleStream = new SimpleStream();
console.log('simple stream data : ', simpleStream.data );
let encryptedStream = new EncryptedStream(simpleStream);
console.log('encrypted stream data : ', encryptedStream.data);
let compressedStream = new CompressedStream(encryptedStream);
console.log('compress stream data : ', compressedStream.data);
// common usage stye:
// new CompressedStream(new EncryptedStream(new SimpleStream()))
class Bevarage {
constructor() {
this.description = 'unknown beverage'
}
getDescription() {
return (this.description);
}
// abstract method
cost() {}
}
class Espresso extends Bevarage {
constructor() {
super();
this.description = "Espresso shot";
}
cost() {
return 1.99;
}
}
class HouseBlend extends Bevarage {
constructor() {
super();
this.description = "House blend coffee"
}
cost() {
return .89;
}
}
class Decaf extends Bevarage {
constructor() {
super();
this.description = "Decaf"
}
cost() {
return 1.05;
}
}
class DarkRoast extends Bevarage {
constructor() {
super();
this.description = "Dark Roast"
}
cost() {
return 1;
}
}
class CondimentDecorator extends Bevarage {
constructor() {
super();
this.decorator = null;
}
getDescription() {}
}
class Mocha extends CondimentDecorator {
constructor(beverage) {
super();
this.beverage = beverage;
}
getDescription() {
return `${this.beverage.getDescription()} + Mocha`;
}
cost() {
return this.beverage.cost() + .20;
}
}
class Whip extends CondimentDecorator {
constructor(beverage) {
super();
this.beverage = beverage;
}
getDescription() {
return `${this.beverage.getDescription()} + Whip`;
}
cost() {
return this.beverage.cost() + .25;
}
}
let esp = new Espresso();
console.log(esp.getDescription(), esp.cost()); // Espresso shot 1.99
let drost = new DarkRoast();
console.log(drost.getDescription(), drost.cost()); // Dark Roast 1
drost = new Mocha(drost);
console.log(drost.getDescription(), drost.cost()); // Dark Roast + Mocha 1.2
drost = new Mocha(drost);
console.log(drost.getDescription(), drost.cost()); // Dark Roast + Mocha + Mocha 1.4
drost = new Whip(drost);
console.log(drost.getDescription(), drost.cost()); // Dark Roast + Mocha + Mocha + Whip 1.65
alternate way to call - nested decorators
let esp2 = new Mocha( new Mocha ( new Mocha( new Espresso())))
console.log(esp2.getDescription(), esp2.cost())