Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative for visitor pattern in TypeScript (avoiding instanceof conditionals)

Let's assume I have an interface

interface I <T extends InternalResult> {
    doStuff(): T;
}

and two concrete classes implementing this interface:

class A implements I <InternalAResult> {
    doStuff(): InternalAResult {
        // implementation
    }
}

class B implements I <InternalBResult> {
    doStuff(): InternalBResult {
        // implementation
    }
}

My client cannot execute doStuff() directly, but needs an instance of a class implementing an Executer interface, like one of those:

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;
    }
}

For such cases I always used the Visitor pattern in Java. I could create a visitor, pass two Executer instances to it and implement the visit method with an A and a B overloading, creating a conditional-free solution, like this:

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);
    }
}

Now, there is no such thing as method overloading in TypeScript / JavaScript. But what is the alternative to creating a conditional like this?:

if (it instanceof A)
    aExecuter.execute(it);
else if (it instanceof B)
    bExecuter.execute(it);

Note: I want to have the A class know nothing about AExecuter. This would increase the coupling between those two classes and I could not switch the implementation of AExecutor easily.

like image 554
ggradnig Avatar asked Jul 04 '18 08:07

ggradnig


3 Answers

You can use the approach the typescript compiler team used and have a field that discriminates against the type of field, replacing instanceof with a simple string/number comparison, which is probably less expensive (although you should test for your use case):

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;
        }
    }
}

Another approach which requires less writing (but is less type safe) is to use the constructor name as a key for access the correct method:

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);
    }
}
like image 125
Titian Cernicova-Dragomir Avatar answered Nov 04 '22 17:11

Titian Cernicova-Dragomir


Maybe link the executor directly to the class, like:

A.prototype.Executor = ExecutorA;
B.prototype.Executor = ExecutorB;

So then you can just do

(new it.Executor).execute(it)

Alternatively one could use a Map to link the class and the Executor:

const executors: Map<I, Executor> = new Map();
executors.set(A, ExecutorA).set(B, ExecutorB);

which can be used as:

(new executors.get( it.constructor )).execute(it)
like image 1
Jonas Wilms Avatar answered Nov 04 '22 17:11

Jonas Wilms


You can always skip the visitor pattern completely and use a multimethod library instead, then you can simplify your code to something like this:

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())

It less type-safe (compiler won't warn you if you put a multimethod together incorrectly - but it's trivial code), but it is a much simpler solution.

like image 1
kapitalny Avatar answered Nov 04 '22 17:11

kapitalny