Türünü koruyan bir Nesne Fabrikası nasıl yapılır

Dec 23 2020

Tüm arabirimlerimin uygulamalarını başlatmak için aşağıdaki nesne fabrikasını yarattım:

interface SomeInterface {
  get(): string;
}

class Implementation implements SomeInterface {
  constructor() {}
  get() {
    return "Hey :D";
  }
}

type Injectable = {
  [key: string]: () => unknown;
};

// deno-lint-ignore prefer-const
let DEFAULT_IMPLEMENTATIONS: Injectable = {
  SomeInterface: () => new Implementation(),
};

let MOCK_IMPLEMENTATIONS: Injectable = {};

class Factory {
  static getInstance(interfaceName: string, parameters: unknown = []) {
    if (MOCK_IMPLEMENTATIONS[interfaceName])
      return MOCK_IMPLEMENTATIONS[interfaceName]();
    return DEFAULT_IMPLEMENTATIONS[interfaceName]();
  }

  static mockWithInstance(interfaceName: string, mock: unknown) {
    MOCK_IMPLEMENTATIONS[interfaceName] = () => mock;
  }
}

export const ObjectFactory = {
  getInstance<T>(name: string): T {
    return Factory.getInstance(name) as T;
  },

  mockWithInstance: Factory.mockWithInstance,
};

const impl = ObjectFactory.getInstance<SomeInterface>("SomeInterface");

Gördüğünüz gibi, bu Fabrika bu arayüzlerin somutlaştırılmasına ve alay edilmesine izin verir. Asıl sorun, atamalarda türü korumak için bu işlevi arayüzün adı VE arayüzle çağırmak zorunda olmamdır:

ObjectFactory.getInstance<SomeInterface>("SomeInterface")

Bu soruyu gördüm ama Basearayüz kullanma fikrinden hoşlanmıyorum . Dahası, bu yaklaşım türü de tutmuyor.

İdeal olarak, yaklaşımımı kullanmak isterdim, ancak arayüzü kullanmak zorunda kalmadan, yani sadece arayüzün adını kullanmak istiyorum.

Yan not: beyanı, Injectablebu kodun çalışmasını sağlamak için bir hacklemedir, ideal olarak, yalnızca uygulama adını, yani:

let DEFAULT_IMPLEMENTATIONS = {
    SomeInterface: Implementation
}

Yanıtlar

1 jcalz Dec 23 2020 at 09:48

Desteklemeyi önemsediğiniz sabit bir isim-tip eşleştirmeler listesine sahip olduğunuz için, buradaki genel yaklaşım, Tbu eşlemeyi temsil eden nesne türü açısından düşünmektir ve daha sonra desteklenen herhangi bir arayüz adı için K extends keyof T, şu işlevlerle ilgileneceksiniz: özelliği bu isimde döndürür ... yani türdeki işlevler () => T[K]. Bunu söylemenin bir başka yolu da, fabrikanıza keyoftürler vermeye yardımcı olmak için türleri kullanacağımızdır.

{"SomeInterface": SomeInterface; "Date": Date}For gibi yalnızca somut bir tür kullanacağız T, ancak bundan sonra derleyici Tgenel ise daha kolay bir zamana sahip olacak . İşte bir yapımcının olası bir genel uygulaması ObjectFactory:

function makeFactory<T>(DEFAULT_IMPLEMENTATIONS: { [K in keyof T]: () => T[K] }) {
  const MOCK_IMPLEMENTATIONS: { [K in keyof T]?: () => T[K] } = {};
  return {
    getInstance<K extends keyof T>(interfaceName: K) {
      const compositeInjectable: typeof DEFAULT_IMPLEMENTATIONS = {
        ...DEFAULT_IMPLEMENTATIONS,
        ...MOCK_IMPLEMENTATIONS
      };
      return compositeInjectable[interfaceName]();
    },
    mockWithInstance<K extends keyof T>(interfaceName: K, mock: T[K]) {
      MOCK_IMPLEMENTATIONS[interfaceName] = () => mock;
    }
  }
}

Sürümünüzü, tür iddialarından kaçınmak için derleyicinin çoğunlukla güvenli tür olarak doğrulayabileceği bir şeye dönüştürdüm . Başından geçelim.

makeFactoryFonksiyon nesne haritalama tipinde geneldir Tve adlandırılmış bir argüman alır DEFAULT_IMPLEMENTATIONSÇeşidi { [K in keyof T]: () => T[K] }. Bu, anahtarları ile aynı olan , ancak özellikleri bir tür değeri döndüren sıfır arg işlevli işlevler olan eşlenmiş bir türdür . Var olanınızın nasıl olduğunu görebilirsiniz : her özellik, karşılık gelen arayüzün bir değerini döndüren sıfır-arg işleviydi.KTT[K]DEFAULT_IMPLEMENTATIONS

Fonksiyon uygulamasının içinde yaratıyoruz MOCK_IMPLEMENTATIONS. Bu değişken, hemen hemen aynı türe sahiptir, DEFAULT_IMPLEMENTATIONSancak özellikleri isteğe bağlıdır ( ?içinde isteğe bağlı değiştiriciden etkilendiği gibi [K in keyof T]?).

İşlev, iki yöntemi olan fabrikanın kendisini döndürür:

getInstanceYöntem, genel bir Karabirim adı tipine, ve geri dönüş değeri tiptedir T[K], tekabül eden arayüz özelliği. Bunu birleştirme DEFAULT_IMPLEMENTATIONSve nesne yaymaMOCK_IMPLEMENTATIONS yoluyla uyguluyorum ve bunun ile aynı tür olduğunu ekleyerek uyguluyorum . Sonra onu ile indeksliyoruz ve çağırıyoruz.compositeInjectableDEFAULT_IMPLEMENTATIONSinterfaceName

mockWithInstanceYöntem ayrıca, genel olarak bir Karabirim adı tipi ve türü bir parametre kabul K(arayüz adı) ve tip bir parametresinin T[K](tekabül eden arayüz).


Eylemde görelim:

const ObjectFactory = makeFactory({
  SomeInterface: (): SomeInterface => new Implementation(),
  Date: () => new Date()
});

console.log(ObjectFactory.getInstance("SomeInterface").get().toUpperCase()); // HEY :D
ObjectFactory.mockWithInstance("SomeInterface", { get: () => "howdy" });
console.log(ObjectFactory.getInstance("SomeInterface").get().toUpperCase()); // HOWDY
console.log(ObjectFactory.getInstance("Date").getFullYear()); // 2020

Bunların hepsi beklediğiniz gibi çalışıyor. İstenilen nesne ile ObjectFactoryarayarak yapıyoruz . Burada, özelliğin bir tür değeri döndürdüğünü ekledim (aksi takdirde derleyici, hangisinin istediğinizden daha spesifik olabileceği sonucuna varacaktır ).makeFactoryDEFAULT_IMPLEMENTATIONSSomeInterfaceSomeInterfaceImplementation

Sonra derleyicinin uygun argümanlar ile çağırmamıza ObjectFactory.getInstance()ve ObjectFactory.mockWithInstance()beklenen türleri döndürmemize izin verdiğini ve çalışma zamanında da çalıştığını görebiliriz.


Koda oyun alanı bağlantısı