goog.delegate.DelegateRegistry<T>
Module ID | |
---|---|
Extends |
|
Delegates provide a system for hygienic modification of a delegating class's behavior. The basic idea is that, rather than monkey-patching prototype methods, a class can instead provide extension points by calling out to delegates. Later code can then register delegates, and when the delegating class is instantiated, any registered delegates will be instantiated and returned.
The usage has four parts:
- A delegate interface is defined to provide specific overridable hooks.
This can be a simple function
@typedef
, or an entire@interface
or@record
. - A delegate registry for this interface is instantiated, often as a static field on the interface.
- One or more delegates are defined that implement this interface. Delegates are registered with the registry. Different registry classes support different policies for registering more than one delegate.
- After delegates are registered, the delegating class asks the registry for the list of delegates, which are then instantiated if necessary.
In some circumstances (particularly if a delegate method will be called from multiple places) it may make sense to provide an additional wrapper between the delegate list and the delegating (sometimes called "modded") class, to ensure that the delegates are used correctly.
Example usage
For example, consider a class Foo
that wants to provide a few extension
points for the behaviors zorch
and snarf
. We can set up the delegation
as follows:
const DelegateRegistry = goog.require('goog.delegate.DelegateRegistry');
const delegates = goog.require('goog.delegate.delegates');
class Foo {
constructor() {
/** @private @const {!Array<!Foo.Delegate>} */
this.delegates_ = Foo.registry.delegates();
}
frobnicate(x, y, z) {
const w = delegates.callFirst(this.delegates_, d => d.zorch(x, y));
return this.delegates_.map(d => d.snarf(z, w));
}
}
/** @interface */
Foo.Delegate = class {
zorch(a, b) {}
snarf(a, b) {}
}
/** @const {!DelegateRegistry<!Foo.Delegate>} */
Foo.registry = new DelegateRegistry();
A file inserted later in the bundle can define a delegate and register itself with the registry:
/** @implements {Foo.Delegate} */
class WibblyFooDelegate {
zorch(a, b) { return a + b; }
snarf(a, b) { return a - b; }
}
Foo.registry.registerClass(WibbyFooDelegate);
In many cases, the delegates need to be initialized with an instance of the
modded class. To support this, a function may be passed to the delegates()
method to override how the constructor is called.
Multiple Delegates
Two different registry classes are defined, each with a different policy for
how to handle multiple delegates. The simpler one, DelegateRegistry
,
allows multiple delegates to be registered and returns them in the order they
were registered. If only one delegate is expected,
DelegateRegistry.prototype.expectAtMostOneDelegate()
performs assertions
(in debug mode) that at most one delegate is added, though in production
mode it will still register them all - The use of delegate()
or
goog.delegate.delegates.callFirst()
is recommended in this case to ensure
reasonable behavior.
The more sophisticated one, DelegateRegistry.Prioritized
, requires passing
a unique priority to each delegate registration (collisions are asserted in
debug mode, but will fall back to registration order in production).
Wrapped Delegator
In some cases it makes sense to wrap the delegate list in a dedicated delegator object, rather than having the modded class use it directly:
/** @record */
class MyDelegateInterface {
/** @param {number} arg */
foo(arg) {}
/** @return {number|undefined} */
bar() {}
/** @return {string} */
baz() {}
}
class MyDelegator {
/** @param {!Array<!MyDelegateInterface>} delegates */
constructor(delegates) { this.delegates_ = delegates; }
/** @param {number} */
foo(arg) { this.delegates_.forEach(d => d.foo(arg)); }
/** @return {number} */
bar() {
const result =
delegates.callUntilNotNullOrUndefined(this.delegates_, d => d.bar());
return result != null ? result : 42;
}
/** @return {!Array} */
baz() { return this.delegates_.map(d => d.baz()); }
}
In this example, the modded class will call into the delegates via the wrapper class, ensuring that the correct calling convention is always used.
new DelegateRegistry<T>()
Parameters | None. |
---|