Reputation: 187
I'm trying to figure out how to provide a type definition that corresponds to "all classes that implement some abstract class." Take the following code example:
abstract class AbstractFoo {
abstract foo()
}
class Concrete1 extends AbstractFoo {
foo() { ... }
}
class Concrete1 extends AbstractFoo {
foo() { ... }
}
Now, I'm trying to create a map that goes from string
to one of the concrete classes. Note that I am not trying to map into instances of the concrete classes. See the following:
const myMap: Map<string, typeINeedHelpWith> = new Map()
myMap.set('concrete1string', Concrete1)
myMap.set('concrete2string', Concrete2)
const instantiatedConcrete1 = new myMap.get('concrete1string')(...)
Is there a type definition for typeINeedHelpWith
that would let me accomplish this?
Upvotes: 2
Views: 1235
Reputation: 18943
Use a function that returns the concrete instance as map value.
Update: The below suggestion is just valid for deno
As a suggestion, it is better to use a Record
type instead of a Map
because otherwise wrong keys
(mymap.get("concrete3")()
) will be noticed as runtime exceptions.
abstract class AbstractFoo {
abstract foo(): number;
}
class Concrete1 extends AbstractFoo {
foo() {
return 1;
}
}
class Concrete2 extends AbstractFoo {
foo() {
return 2;
}
}
const myMap: Record<string, () => AbstractFoo> = {
"concrete1": () => new Concrete1(),
"concrete2": () => new Concrete2(),
};
const instantiatedConcrete1 = myMap.concrete1();
let fooResult = instantiatedConcrete1.foo();
As noted in the comments, notice that I've used a factory pattern for creating objects.
This is recognized as a good design pattern, but when not required a simpler constructor based solution may be more appropriate:
const myRec: Record<string, new() => AbstractFoo> = {
"concrete1": Concrete1,
"concrete2": Concrete2,
};
const iConcrete1 = new myRec.concrete1();
The sintax new() => AbstractFoo
define the signature of a constructor that takes no arguments and returns object with shape AbstractFoo
.
Upvotes: 2
Reputation: 5524
You can try something like this, using mapped types:
abstract class AbstractFoo {
abstract foo(): string;
}
class Concrete1 extends AbstractFoo {
foo() { return "Concrete 1" }
}
class Concrete2 extends AbstractFoo {
foo() { return "Concrete 2" }
}
interface Mapping {
'concrete1string': Concrete1,
'concrete2string': Concrete2
}
class MyHeplerMap {
maps: Mapping = {
'concrete1string': new Concrete1(),
'concrete2string': new Concrete2()
};
get<T extends keyof Mapping>(type: T): Mapping[T] {
return this.maps[type];
}
}
const map = new MyHeplerMap();
const c = map.get('concrete1string');
console.log(c.foo());
Please see playground link.
Upvotes: 0
Reputation: 1264
AFAIK the only type available is typeof AbstractFoo
, that is the constructor type. Like typeof Array
refers to the type of the Array
constructor, while the type Array
refers to the type of array instances.
Both Concrete1
and Concrete2
extend that type (more precisely typeof Concrete1
and typeof Concrete2
extend typeof AbstractFoo
)
But you can't create a map like the following:
const myMap: Map<string, typeof AbstractFoo> = new Map()
myMap.set('concrete1string', Concrete1)
myMap.set('concrete2string', Concrete2)
Nay, you can, but the return type of myMap.get('concrete1string')
is typeof AbstractFoo
, not typeof Concrete1
. You cannot call, with new
, a constructor of type typeof AbstractFoo
.
Upvotes: 0