प्रकार बनाए रखने के लिए ऑब्जेक्ट फैक्ट्री कैसे बनाएं

Dec 23 2020

मैंने अपने सभी इंटरफेस के कार्यान्वयन को लागू करने के लिए निम्नलिखित ऑब्जेक्ट फैक्ट्री बनाई है:

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
}

जवाब

1 jcalz Dec 23 2020 at 09:48

चूँकि आपके पास नाम-टू-टाइप मैपिंग की एक निश्चित सूची है जिसका आप समर्थन करने में ध्यान रखते हैं, यहाँ सामान्य दृष्टिकोण 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 माध्यम से लागू करता हूं , और एनोटेटिंग करता हूं कि यह उसी प्रकार है जैसे कि । फिर हम इसके साथ इसे इंडेक्स करते हैं और इसे कॉल करते हैं।compositeInjectableDEFAULT_IMPLEMENTATIONSinterfaceName

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कॉल करके बनाते हैं । यहां मैंने एनोटेट किया है कि संपत्ति प्रकार का मान लौटाती है (अन्यथा कंपाइलर अनुमान लगाएगा जो आप की तुलना में अधिक विशिष्ट हो सकता है)।makeFactoryDEFAULT_IMPLEMENTATIONSSomeInterfaceSomeInterfaceImplementation

फिर हम देख सकते हैं कि संकलक हमें फोन की सुविधा देता है ObjectFactory.getInstance()और ObjectFactory.mockWithInstance()उचित तर्क के साथ और उम्मीद प्रकार लौटने, और यह भी रनटाइम पर काम करता है।


कोड के लिए खेल का मैदान लिंक