प्रकार बनाए रखने के लिए ऑब्जेक्ट फैक्ट्री कैसे बनाएं
मैंने अपने सभी इंटरफेस के कार्यान्वयन को लागू करने के लिए निम्नलिखित ऑब्जेक्ट फैक्ट्री बनाई है:
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");
जैसा कि आप देख सकते हैं, यह फैक्ट्री आपको उन इंटरफेस के इंस्टेंटेशन और मॉकिंग की सुविधा देती है। मुख्य समस्या यह है कि मुझे इस फ़ंक्शन को इंटरफ़ेस और इंटरफ़ेस के नाम के साथ असाइनमेंट में रखने के लिए कॉल करना है:
ObjectFactory.getInstance<SomeInterface>("SomeInterface")
मैंने इस प्रश्न को देखा है , लेकिन मुझे Base
इंटरफ़ेस का उपयोग करने का विचार पसंद नहीं है। इसके अलावा, वह दृष्टिकोण न तो प्रकार रखता है और न ही।
आदर्श रूप में, मैं अपने दृष्टिकोण का उपयोग करना चाहता हूं, लेकिन इंटरफ़ेस का उपयोग किए बिना, अर्थात केवल इंटरफ़ेस के नाम का उपयोग करें।
साइड नोट: की घोषणा Injectable
उस कोड को काम करने के लिए एक हैक है, आदर्श रूप से, मैं केवल कार्यान्वयन नाम का उपयोग करने में सक्षम होना चाहूंगा, जो है:
let DEFAULT_IMPLEMENTATIONS = {
SomeInterface: Implementation
}
जवाब
चूँकि आपके पास नाम-टू-टाइप मैपिंग की एक निश्चित सूची है जिसका आप समर्थन करने में ध्यान रखते हैं, यहाँ सामान्य दृष्टिकोण T
इस प्रकार की मैपिंग का प्रतिनिधित्व करने वाले ऑब्जेक्ट प्रकार के बारे में सोचना है , और फिर किसी भी समर्थित इंटरफ़ेस नाम के लिए K extends keyof T
, आप उन कार्यों से निपटेंगे जो उस नाम पर संपत्ति लौटाएं ... अर्थात्, प्रकार के कार्य () => T[K]
। यह कहने का एक और तरीका यह है कि हम आपके कारखाने को प्रकार देने में मदद करने के लिए उपयोग keyofऔर लुकअप प्रकार होंगे ।
हम केवल के लिए एक ठोस प्रकार का उपयोग कर {"SomeInterface": SomeInterface; "Date": Date}
रहे हैं T
, लेकिन क्या T
सामान्य है अगर कंपाइलर चीजों का एक आसान समय इस प्रकार है। यहाँ एक 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;
}
}
}
मैंने आपके संस्करण को किसी ऐसी चीज़ में बदल दिया, जो संकलक ज्यादातर सुरक्षित प्रकार के रूप में सत्यापित कर सकता है, ताकि प्रकार के दावे से बचा जा सके । चलो इसके माध्यम से चलते हैं।
makeFactory
समारोह वस्तु मानचित्रण प्रकार में सामान्य है T
, और एक तर्क नामित लेता DEFAULT_IMPLEMENTATIONS
प्रकार के { [K in keyof T]: () => T[K] }
। यह एक मैप किया हुआ प्रकार है, जिसकी कुंजी K
समान होती है, T
लेकिन जिनके गुण शून्य-arg फ़ंक्शन होते हैं जो प्रकार का मान लौटाते हैं T[K]
। आप देख सकते हैं कि आपकी मौजूदा स्थिति कैसी DEFAULT_IMPLEMENTATIONS
थी: प्रत्येक संपत्ति एक शून्य-आर्ग फ़ंक्शन थी जो संबंधित इंटरफ़ेस का मान लौटा रही थी।
फ़ंक्शन कार्यान्वयन के अंदर, हम बनाते हैं MOCK_IMPLEMENTATIONS
। यह चर के रूप में लगभग एक ही प्रकार है DEFAULT_IMPLEMENTATIONS
, लेकिन गुण होने के साथ वैकल्पिक (के रूप में optionality संशोधक से प्रभावित ?
में [K in keyof T]?
)।
फ़ंक्शन फ़ैक्टरी को स्वयं लौटाता है, जिसमें दो विधियाँ हैं:
getInstance
विधि में सामान्य है K
, इंटरफ़ेस नाम के प्रकार, और वापसी मान प्रकार का है T[K]
, इसी इंटरफेस संपत्ति। मैं इसे मर्ज करके DEFAULT_IMPLEMENTATIONS
और ऑब्जेक्ट स्प्रेड केMOCK_IMPLEMENTATIONS
माध्यम से लागू करता हूं , और एनोटेटिंग करता हूं कि यह उसी प्रकार है जैसे कि । फिर हम इसके साथ इसे इंडेक्स करते हैं और इसे कॉल करते हैं।compositeInjectable
DEFAULT_IMPLEMENTATIONS
interfaceName
mockWithInstance
विधि को भी सामान्य में है K
, इंटरफ़ेस नाम के प्रकार, और प्रकार का एक पैरामीटर स्वीकार करता है K
(इंटरफ़ेस नाम), और प्रकार का एक पैरामीटर T[K]
(इसी इंटरफेस)।
आइए इसे कार्रवाई में देखते हैं:
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
यह सब काम करता है जैसा कि मुझे लगता है कि आप उम्मीद करते हैं। हम इच्छित वस्तु के साथ ObjectFactory
कॉल करके बनाते हैं । यहां मैंने एनोटेट किया है कि संपत्ति प्रकार का मान लौटाती है (अन्यथा कंपाइलर अनुमान लगाएगा जो आप की तुलना में अधिक विशिष्ट हो सकता है)।makeFactory
DEFAULT_IMPLEMENTATIONS
SomeInterface
SomeInterface
Implementation
फिर हम देख सकते हैं कि संकलक हमें फोन की सुविधा देता है ObjectFactory.getInstance()
और ObjectFactory.mockWithInstance()
उचित तर्क के साथ और उम्मीद प्रकार लौटने, और यह भी रनटाइम पर काम करता है।
कोड के लिए खेल का मैदान लिंक