[英]How to construct TypeScript types for abstract factory pattern
我試圖弄清楚如何鍵入private factories: Record<...>
,它將包含aKey: aFactoryInstance
的鍵值對。 我試過Record<string, TemplateFactory>
,它有兩個問題; 1. 它們的鍵不只是任何字符串,而是一個特定的字符串,以及 2. TemplateFactory 是抽象 class,我所擁有的值是來自該抽象工廠的派生類的實例。
我發現這個關於在 typescript 中創建工廠 class 的 SO 線程,這也有我的第二個問題是:
元素隱含地具有“任何”類型,因為...
但是里面的注釋在這里不適用,因為它們並沒有真正實現抽象工廠模式,我的印象是。
abstract class TemplateFactory {}
class FirstTemplateFactory extends TemplateFactory {}
class SecondTemplateFactory extends TemplateFactory {}
const AvailableTemplate = Object.freeze({
first: FirstTemplateFactory,
second: SecondTemplateFactory,
});
class TemplateCreator {
private factories: Record<string, unknown>; // 👈 How to type this record? It will be an object of: { aKey: aFactoryInstance }
constructor() {
this.factories = {};
Object.keys(AvailableTemplate).forEach((key) => {
this.factories[key] = new AvailableTemplate[key](); // 👈 "Element implicitly has an 'any' type because expression of type 'string' can't be used to index"
});
}
}
我個人的觀點是,擁有一個依賴於全局常量AvailableTemplate
的class TemplateCreator
是一個糟糕的設計。 將模板映射移動到 class 內部或將其作為構造函數的參數。
下一個考慮是我們希望它在區分模板類型方面有多嚴格。 我們知道我們總會得到一個Template
。 我們關心它是FirstTemplate
還是SecondTemplate
嗎? 我通常傾向於過度打字。
我們需要AvailableTemplate
映射中任何 class 的以下內容:
new
來實例化Template
的create
方法我們將使用泛型來描述我們的 map object 並確保它extends
了這些要求。 當我們說一個類型extends Record<string, Value>
我們可以將值限制為Value
但仍然可以訪問我們類型的特定鍵。 我們的類型擴展了 Record 但它本身不是 Record 並且沒有索引簽名。
當從實例到類而不是相反時(由於extends
的性質),我們得到了更好的類型推斷,所以我們的 class 將取決於其工廠的類型。
class TemplateCreator<T extends Record<string, BaseFactory>> {
private factories: T;
我們將使用映射類型將實例映射轉換為類/構造函數的映射。
type ToConstructors<T> = {
[K in keyof T]: new() => T[K];
}
代碼中 object 的實際映射總是需要一些斷言,因為Object.keys
和Object.entries
使用類型string
而不是keyof T
constructor(classes: ToConstructors<T>) {
// we have to assert the type here
// unless you want to pass in instances instead of classes
this.factories = Object.fromEntries(
Object.entries(classes).map(
([key, value]) => ([key, new value()])
)
) as T;
}
我認為可以避免第二個斷言,但目前我還沒有 class 本身根據密鑰識別創建的模板的特定類型。 如果我們希望TemplateCreator
的面向公眾的 API 返回匹配的類型,我們可以在此處使用快捷方式並斷言它。
// needs to be generic in order to get a specific template type
createForType<K extends keyof T>(type: K): ReturnType<T[K]['create']> {
return this.factories[type].create() as ReturnType<T[K]['create']>;
}
如果我們不關心特殊性並且總是返回Template
就可以了,那么我們就不需要斷言或泛型。 我們仍然可以限制密鑰。
createForType(type: keyof T): Template {
return this.factories[type].create();
}
完整代碼:
//------------------------BASE TYPES------------------------//
interface Template {
}
interface BaseFactory {
create(): Template;
}
type ToConstructors<T> = {
[K in keyof T]: new () => T[K];
}
//------------------------THE FACTORY------------------------//
class TemplateCreator<T extends Record<string, BaseFactory>> {
private factories: T;
constructor(classes: ToConstructors<T>) {
// we have to assert the type here
// unless you want to pass in instances instead of classes
this.factories = Object.fromEntries(
Object.entries(classes).map(
([key, value]) => ([key, new value()])
)
) as T;
}
// this method needs to be generic
// in order to get a specific template type
createForType<K extends keyof T>(type: K): ReturnType<T[K]['create']> {
// I think this can be improved so that we don't need to assert
// right now it is just `Template`
return this.factories[type].create() as ReturnType<T[K]['create']>;
}
}
//------------------------OUR CLASSES------------------------//
abstract class TemplateFactory {
abstract create(): Template;
}
interface FirstTemplate extends Template {
firstProp: string;
}
class FirstTemplateFactory extends TemplateFactory {
create(): FirstTemplate {
return { firstProp: "first" };
}
}
interface SecondTemplate extends Template {
secondProp: string;
}
class SecondTemplateFactory extends TemplateFactory {
create(): SecondTemplate {
return { secondProp: "second" };
}
}
// -----------------------TESTING-------------------------- //
const AvailableTemplate = Object.freeze({
first: FirstTemplateFactory,
second: SecondTemplateFactory,
});
const myTemplateCreator = new TemplateCreator(AvailableTemplate);
const first = myTemplateCreator.createForType('first'); // type: FirstTemplate
const second = myTemplateCreator.createForType('second'); // type: SecondTemplate
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.