Türünü koruyan bir Nesne Fabrikası nasıl yapılır
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 Base
arayü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ı, Injectable
bu 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
Desteklemeyi önemsediğiniz sabit bir isim-tip eşleştirmeler listesine sahip olduğunuz için, buradaki genel yaklaşım, T
bu 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 T
genel 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.
makeFactory
Fonksiyon nesne haritalama tipinde geneldir T
ve 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.K
T
T[K]
DEFAULT_IMPLEMENTATIONS
Fonksiyon uygulamasının içinde yaratıyoruz MOCK_IMPLEMENTATIONS
. Bu değişken, hemen hemen aynı türe sahiptir, DEFAULT_IMPLEMENTATIONS
ancak ö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:
getInstance
Yöntem, genel bir K
arabirim adı tipine, ve geri dönüş değeri tiptedir T[K]
, tekabül eden arayüz özelliği. Bunu birleştirme DEFAULT_IMPLEMENTATIONS
ve nesne yaymaMOCK_IMPLEMENTATIONS
yoluyla uyguluyorum ve bunun ile aynı tür olduğunu ekleyerek uyguluyorum . Sonra onu ile indeksliyoruz ve çağırıyoruz.compositeInjectable
DEFAULT_IMPLEMENTATIONS
interfaceName
mockWithInstance
Yöntem ayrıca, genel olarak bir K
arabirim 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 ObjectFactory
arayarak 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 ).makeFactory
DEFAULT_IMPLEMENTATIONS
SomeInterface
SomeInterface
Implementation
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ı