假设我有一个界面
interface I <T extends InternalResult> {
doStuff(): T;
}
和两个实现此接口的具体类:
class A implements I <InternalAResult> {
doStuff(): InternalAResult {
// implementation
}
}
class B implements I <InternalBResult> {
doStuff(): InternalBResult {
// implementation
}
}
我的客户端无法直接执行doStuff()
,但需要一个实现Executer
接口的类的实例,例如其中之一:
interface Executer <T, R> {
execute(it: T): R;
}
class AExecuter implements Executer <A, AResult> {
execute(it: A): AResult {
let internalResult = it.doStuff();
// ... do something specific with internalResult create result
return result;
}
}
class BExecuter implements Executer <B, BResult> {
execute(it: B): BResult {
let internalResult = it.doStuff();
// ... do something other specific with internalResult create result
return result;
}
}
在这种情况下,我总是在Java中使用 Visitor 模式。我可以创建一个访问者,将两个Executer
实例传递给它,并通过visit
和A
重载实现B
方法,从而创建一个无条件的解决方案,如下所示:
class Visitor {
private aExecuter: AExecuter;
private bExecuter: BExecuter;
visit(it: A): Result {
return this.aExecuter.execute(it);
}
visit(it: B): Result {
return this.bExecuter.execute(it);
}
}
现在,在TypeScript / JavaScript中没有方法重载之类的东西。但是创建这样的条件的替代方法是什么?:
if (it instanceof A)
aExecuter.execute(it);
else if (it instanceof B)
bExecuter.execute(it);
注意:我想让A
类对AExecuter
一无所知。这将增加这两个类之间的耦合,并且我无法轻松切换AExecutor
的实现。
答案 0 :(得分:1)
也许将执行程序直接链接到该类,例如:
A.prototype.Executor = ExecutorA;
B.prototype.Executor = ExecutorB;
那么你就可以做
(new it.Executor).execute(it)
或者可以使用Map链接类和执行器:
const executors: Map<I, Executor> = new Map();
executors.set(A, ExecutorA).set(B, ExecutorB);
可以用作:
(new executors.get( it.constructor )).execute(it)
答案 1 :(得分:1)
您可以使用打字稿compiler team使用的方法,并使用一个区分字段类型的字段,用简单的字符串/数字比较代替instanceof
,这可能会便宜一些(尽管您应该测试您的用例):
enum Types {
A, B
}
interface I <T extends InternalResult> {
readonly type : Types;
doStuff(): T;
}
class A implements I <AResult> {
readonly type = Types.A
doStuff(): AResult {
return new AResult();
}
}
class B implements I <BResult> {
readonly type = Types.B
doStuff(): BResult {
return new BResult();
}
}
class Visitor {
private aExecuter: AExecuter = new AExecuter();
private bExecuter: BExecuter = new BExecuter();
visit(it: A | B): AResult | BResult {
// Since we don't have a default return, we will get a compiler error if we forget a case
switch(it.type){
case Types.A : return this.aExecuter.execute(it); break;
case Types.B : return this.bExecuter.execute(it); break;
}
}
}
另一种需要较少编写(但类型安全性较低)的方法是使用构造函数名称作为访问正确方法的键:
class Visitor {
private aExecuter: AExecuter;
private bExecuter: BExecuter;
visit(it: A | B): AResult | BResult {
let visitor: (it: A | B) => AResult | BResult = (this as any)['visit' + it.constructor.name];
if(visitor == null) {
throw "Visitor not found"
}
return visitor.call(this, it)
}
visitA(it: A): AResult {
return this.aExecuter.execute(it);
}
visitB(it: B): BResult {
return this.bExecuter.execute(it);
}
}
答案 2 :(得分:1)
您始终可以完全跳过访问者模式,而改用多方法库,然后可以将代码简化为以下形式:
import { multi, method, Multi } from '@arrows/multimethod'
interface IExecute extends Multi {
(it: A): AResult
(it: B): BResult
}
const execute: IExecute = multi(
method(A, (it: A) => it.doStuff()),
method(B, (it: B) => it.doStuff()),
)
// Usage:
execute(new A())
execute(new B())
它的类型安全性较差(如果您错误地将多方法组合在一起,编译器不会警告您-但这是微不足道的代码),但这是一个简单得多的解决方案。
答案 3 :(得分:0)
我不知道被接受的答案,但是我如何设法绕过SELECT
MUID, weekcounter,
STUFF((SELECT ',' + Category
FROM tb EE
WHERE EE.MUID = E.MUID
AND EE.weekcounter = E.weekcounter
AND Ranknum <= 3
FOR XML PATH, TYPE).value(N'.[1]', N'nvarchar(max)'), 1, 1, N'') AS listStr
FROM
tb E
GROUP BY
E.MUID, E.weekcounter
是通过向基类中添加方法(或者您也可以只使用辅助函数)和其他类型属性。
例如
ìnstanceof
像魅力一样工作。编译代码时,将删除类型定义,因此删除了A和B之间的所有循环依赖关系。但是,我发现在解决此问题之后,我发现了新的错误,所以也是,但是也是如此。