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中的访问者模式.我可以创建一个访问者,向它传递两个Executer实例,并使用A和B重载实现visit方法,创建一个无条件解决方案,如下所示:

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的实现.

最佳答案 您可以使用typescript
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);
    }
}
点赞