【发布时间】:2021-05-05 03:13:34
【问题描述】:
我想构建一个可以组合多个对象并使用它们的任何接口的类。
A类可以使用B类和C类的任何接口
B可以使用C的任何接口
C可以使用B的任何接口
我有上述用 JavaScript 编写的功能,我想知道使用 TypeScript 实现相同功能的最佳和正确方法是什么:
import { findLast, isFunction } from "lodash";
class Composite {
constructor(behavior) {
this.behaviors = [];
if (behavior) {
this.add(behavior);
}
}
add(behavior) {
behavior.setClient(this);
this.behaviors.push(behavior);
return this;
}
getMethod(method) {
const b = findLast(this.behaviors, (behavior) =>
isFunction(behavior[method])
);
return b[method].bind(b);
}
}
class Behavior1 {
foo() {
console.log("B1: foo");
}
foo2() {
console.log("B1: foo2");
this.getMethod("bar")();
}
setClient(client) {
this.client = client;
}
getMethod(method) {
return this.client.getMethod(method);
}
}
class Behavior2 {
foo() {
console.log("B2: foo");
this.getMethod("foo2")();
}
bar() {
console.log("B2: bar");
}
setClient(client) {
this.client = client;
}
getMethod(method) {
return this.client.getMethod(method).bind(this);
}
}
const c = new Composite();
c.add(new Behavior1());
c.add(new Behavior2());
c.getMethod("foo")();
c.getMethod("bar")();
// Output:
// B2: foo
// B1: foo2
// B2: bar
// B2: bar
codeandbox 链接:https://codesandbox.io/s/zen-poitras-56f4e?file=/src/index.js
【问题讨论】:
-
只是问。这两种行为都重复了
setClient和getMethod,它不应该是共享接口的一部分吗?此外,组合的getClient返回包含给定方法的最后一个行为,但如果没有匹配的行为怎么办?看起来这里没有涉及到,并且可能会发生空引用异常。另外,最后一个问题,这背后的大局是什么? -
1) 是的,
setClient和getMethod应该属于另一个类,并且每个行为都必须扩展该类。 2) 如果没有匹配的行为,则必须抛出错误。我没有添加这些检查以使示例尽可能简单。 3) 更大的图景是,我正在构建一个 SDK,我希望保持一切分离和模块化,但仍然允许客户端使用单个类来调用支持的所有内容。 -
这个单一的外观是否有任何好处隐藏所有行为但同时使所有调用动态(= 更慢)?我承认我找不到类似方法的明确示例。
-
嗯,它允许您构建不同的功能(通过实现一个新的 Behavior 类),甚至无需触及基类或任何其他 Behavior 类。您只需添加新功能并使用它。您还可以轻松地覆盖接口。
-
我建议你把你的
Composite想象成一个工厂。您可以注册一个创建实例的函数,而不是注册一个实例(这允许更好的生命周期控制)。除了请求方法,您还可以让客户端请求特定键的对象(Javascript)或特定类型/接口(TypeScript)。但是,嘿,这听起来像是一个类固醇工厂,正是 DI 容器。那你为什么不尝试找到一个现有的 JS/TS 容器,比如 Inversify 并使用它而不是编写自己的容器?
标签: javascript typescript design-patterns composition