集合的不可变包装器通过将集合包装在新对象中来使其不可变。在本章中,我们将研究其工作原理以及为何有用。
如果我们想减少对象的接口,可以采用以下方法
这就是包装的样子
class Wrapper {
#wrapped;
constructor(wrapped) {
this.#wrapped = wrapped;
}
allowedMethod1(...args) {
return this.#wrapped.allowedMethod1(...args);
}
allowedMethod2(...args) {
return this.#wrapped.allowedMethod2(...args);
}
}
相关的软件设计模式
为了使集合不可变,我们可以使用包装并从其接口中删除所有破坏性操作。
此技术的一个重要用例是一个对象,它有一个内部可变数据结构,它希望安全地导出该结构而不复制它。“实时”导出也可能是一个目标。该对象可以通过包装内部数据结构并使其不可变来实现其目标。
接下来的两节展示了 Maps 和 Arrays 的不可变包装器。它们都有以下限制
类 ImmutableMapWrapper
生成 Maps 的包装器
class ImmutableMapWrapper {
static _setUpPrototype() {
// Only forward non-destructive methods to the wrapped Map:
for (const methodName of ['get', 'has', 'keys', 'size']) {
ImmutableMapWrapper.prototype[methodName] = function (...args) {
return this.#wrappedMap[methodName](...args);
}
}
}
#wrappedMap;
constructor(wrappedMap) {
this.#wrappedMap = wrappedMap;
}
}
ImmutableMapWrapper._setUpPrototype();
原型的设置必须由静态方法执行,因为我们只能从类内部访问私有字段 .#wrappedMap
。
这是 ImmutableMapWrapper
的实际应用
const map = new Map([[false, 'no'], [true, 'yes']]);
const wrapped = new ImmutableMapWrapper(map);
// Non-destructive operations work as usual:
assert.equal(
wrapped.get(true), 'yes');
assert.equal(
wrapped.has(false), true);
assert.deepEqual(
[...wrapped.keys()], [false, true]);
// Destructive operations are not available:
assert.throws(
() => wrapped.set(false, 'never!'),
/^TypeError: wrapped.set is not a function$/);
assert.throws(
() => wrapped.clear(),
/^TypeError: wrapped.clear is not a function$/);
对于数组 arr
,普通的包装是不够的,因为我们不仅需要拦截方法调用,还需要拦截属性访问,例如 arr[1] = true
。 JavaScript 代理 使我们能够做到这一点
const RE_INDEX_PROP_KEY = /^[0-9]+$/;
const ALLOWED_PROPERTIES = new Set([
'length', 'constructor', 'slice', 'concat']);
function wrapArrayImmutably(arr) {
const handler = {
get(target, propKey, receiver) {
// We assume that propKey is a string (not a symbol)
if (RE_INDEX_PROP_KEY.test(propKey) // simplified check!
|| ALLOWED_PROPERTIES.has(propKey)) {
return Reflect.get(target, propKey, receiver);
}
throw new TypeError(`Property "${propKey}" can’t be accessed`);
},
set(target, propKey, value, receiver) {
throw new TypeError('Setting is not allowed');
},
deleteProperty(target, propKey) {
throw new TypeError('Deleting is not allowed');
},
};
return new Proxy(arr, handler);
}
让我们包装一个数组
const arr = ['a', 'b', 'c'];
const wrapped = wrapArrayImmutably(arr);
// Non-destructive operations are allowed:
assert.deepEqual(
wrapped.slice(1), ['b', 'c']);
assert.equal(
wrapped[1], 'b');
// Destructive operations are not allowed:
assert.throws(
() => wrapped[1] = 'x',
/^TypeError: Setting is not allowed$/);
assert.throws(
() => wrapped.shift(),
/^TypeError: Property "shift" can’t be accessed$/);