Instead of subclassing, you can wrap an existing instance, the subject, to add validation, caching or logging. A surrogate keeps the subject's interface, a decorator adds methods, an adapter exposes a different interface, and monkey patching replaces methods on the subject itself, affecting every holder. The built-in Proxy (Proxy and Reflect) builds surrogates generically.
class Greeter { greet() { return 'Hello'; } }
const relay = (subject) => Object.assign(Object.create(Object.getPrototypeOf(subject)), {
greet: () => `${subject.greet()} world`, // surrogate: same interface
});
const adapt = (subject) => ({ welcome: (who) => `${subject.greet()}, ${who}!` }); // adapter
function patch(subject) { // monkey patching mutates the subject
const original = subject.greet; // keep it, or greet() recurses
subject.greet = function () { return `${original.call(this)} there`; };
}
const g = new Greeter();
console.log(relay(g).greet(), relay(g) instanceof Greeter, adapt(g).welcome('Ann'));
patch(g); console.log(g.greet(), new Greeter().greet());Hello world true Hello, Ann! Hello there Hello