Преимущества использования композиции/делегирования по сравнению с наследованием в JavaScript и других языках хорошо задокументированы:
Замените наследование делегированием
Одна из главных причин, по которой многие разработчики избегают написания кода на основе делегирования, заключается в том, что это замедляет разработку из-за необходимости писать кучу шаблонов для пересылки вызовов функций делегату. Прокси могут значительно сократить объем необходимого кода. Что, если бы вы могли написать только это:
class MyClass {
constructor(delegate) {
this.myDelegate = delegate;
return delegate(this,"myDelegate");
})
myClassMethod() { ... }
methodThatHidesADelgateMethod() { ... }
methodThanEnhancesADelegateMethod(arg) {
const modifiedArg = arg++;
return this.myDelegate(modifiedArg);
}
// all other method calls just forward to the delegate!
}
Во-первых, давайте для ясности напишем код непосредственно в контексте определения класса:
class MyClass {
constructor(delegate) {
this.myDelegate = delegate;
return new Proxy(this,{
get: (target,property) => {
// return value if property is in target instance
if(property in target) return target[property];
// selectively block forwarding
if(block.includes[property]) return;
// get the property from the delegate
const value = target[delegateProperty][property];
if(typeof(value)==="function") {
// if it is a function, proxy it so that scope is correct
return new Proxy(value,{
apply: (f,thisArg,argumentsList) => {
// if trying to call on target, then use delegate
// else call on provided thisArg
const scope = (thisArg===target
? target.myDelegate
: thisArg);
return f.apply(scope,argumentsList);
}
});
}
return value;
}
})
myClassMethod() { ... }
methodThatHidesADelgateMethod() { ... }
methodThanEnhancesADelegateMethod(arg) {
const modifiedArg = arg++;
return this.delegate(modifiedArg);
}
// all other method calls just forward to the delegate!
}
Теперь сделайте настройку прокси многоразовой, инкапсулировав ее в функцию:
// object is the instance from which to delegate
// delegateProperty is the property containing the delegate
// block is an Array of properties to hide on the delegate
const delegate =(object,delegateProperty,block=[]) => {
return new Proxy(object,{
get: (target,property) => {
// return value if property is in target instance
if(property in target) return target[property];
// selectively block forwarding
if(block.includes[property]) return;
// get the property from the delegate
const value = target[delegateProperty][property];
if(typeof(value)==="function") {
// if it is a function, proxy it so that scope is correct
return new Proxy(value,{
apply: (f,thisArg,argumentsList) => {
// if trying to call on target, then use delegate
// else call on provided thisArg
const scope = (thisArg===target
? target[delegateProperty]
: thisArg);
return f.apply(scope,argumentsList);
}
});
}
return value;
}
})
}
Итак, теперь мы можем написать только это:
class MyClass {
constructor(delegate) {
this.delegate = delegate;
return delegate(this,"delegate");
})
myClassMethod() { ... }
methodThatHidesADelgateMethod() { ... }
methodThanEnhancesADelegateMethod(arg) {
const modifiedArg = arg++;
return this.delegate(modifiedArg);
}
// all other method calls just forward to the delegate!
}
Конкретный пример использования этого метода можно увидеть в CacheStore, оболочке кэширования, которая предоставляет localStore API для различных механизмов хранения ключей/значений.