簡體   English   中英

如何為抽象工廠模式構造 TypeScript 類型

[英]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"
    });
  }
}

我個人的觀點是,擁有一個依賴於全局常量AvailableTemplateclass TemplateCreator是一個糟糕的設計。 將模板映射移動到 class 內部或將其作為構造函數的參數。

下一個考慮是我們希望它在區分模板類型方面有多嚴格。 我們知道我們總會得到一個Template 我們關心它是FirstTemplate還是SecondTemplate嗎? 我通常傾向於過度打字。

我們需要AvailableTemplate映射中任何 class 的以下內容:

  • 它可以通過在沒有任何 arguments 的情況下調用new來實例化
  • 它有一個返回Templatecreate方法

我們將使用泛型來描述我們的 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.keysObject.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

Typescript 游樂場鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM