TypeScript中访客模式的替代方法(避免使用instanceof条件)

时间:2018-07-04 08:17:43

标签: javascript typescript

假设我有一个界面

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实例传递给它,并通过visitA重载实现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的实现。

4 个答案:

答案 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之间的所有循环依赖关系。但是,我发现在解决此问题之后,我发现了新的错误,所以也是,但是也是如此。