क्या कार्यात्मक प्रोग्रामिंग निर्भरता इंजेक्शन पैटर्न के लिए एक व्यवहार्य विकल्प है?


21

मैं हाल ही में C # में कार्यात्मक प्रोग्रामिंग नामक एक पुस्तक पढ़ रहा हूं और यह मेरे लिए होता है कि कार्यात्मक प्रोग्रामिंग की अपरिवर्तनीय और स्टेटलेस प्रकृति निर्भरता इंजेक्शन पैटर्न के समान परिणाम प्रदान करती है और संभवतः एक बेहतर दृष्टिकोण भी है, खासकर इकाई परीक्षण के संबंध में।

मैं सराहनीय होता अगर कोई भी व्यक्ति जिसके पास दोनों दृष्टिकोणों के साथ अनुभव है, वह अपने विचारों और अनुभवों को साझा कर सकता है ताकि प्राथमिक प्रश्न का उत्तर दिया जा सके: कार्यात्मक प्रोग्रामिंग निर्भरता इंजेक्शन पैटर्न के लिए एक व्यवहार्य विकल्प है?


10
इससे मुझे कोई मतलब नहीं है, अपरिवर्तनीयता निर्भरता को दूर नहीं करती है।
तेलस्टिन

मैं मानता हूं कि यह निर्भरता को दूर नहीं करता है। यह शायद मेरी समझ है जो गलत है, लेकिन मैंने वह निष्कर्ष निकाला क्योंकि अगर मैं मूल वस्तु को नहीं बदल सकता, तो यह आवश्यक होगा कि मैं इसे किसी भी फ़ंक्शन के साथ (इसे इंजेक्ट) पास करूं जो इसका उपयोग करता है।
मैट Cashatt


5
वहाँ भी है कि कार्यात्मक प्रोग्रामिंग में प्यार करने वाले ओओ प्रोग्रामर को कैसे ट्रिक करना है , जो वास्तव में एक ओओ और एफपी परिप्रेक्ष्य दोनों से डीआई का एक विस्तृत विश्लेषण है।
रॉबर्ट हार्वे

1
यह प्रश्न, इससे जुड़े लेख और स्वीकृत उत्तर भी उपयोगी हो सकते हैं: stackoverflow.com/questions/11276319/ ... डरावने मोनाड शब्द पर ध्यान न दें। जैसा कि रनर अपने जवाब में बताते हैं, यह इस मामले में एक जटिल अवधारणा नहीं है (सिर्फ एक फ़ंक्शन)।
इसकी

जवाबों:


27

निम्न दो कारणों से OOP में निर्भरता प्रबंधन एक बड़ी समस्या है:

  • डेटा और कोड के तंग युग्मन।
  • साइड इफेक्ट्स का सर्वव्यापी उपयोग।

अधिकांश OO प्रोग्रामर डेटा और कोड के तंग युग्मन को पूरी तरह से फायदेमंद मानते हैं, लेकिन यह एक लागत के साथ आता है। परतों के माध्यम से डेटा के प्रवाह को प्रबंधित करना किसी भी प्रतिमान में प्रोग्रामिंग का एक अपरिहार्य हिस्सा है। अपने डेटा और कोड को युग्मित करने से अतिरिक्त समस्या आती है कि यदि आप एक निश्चित बिंदु पर फ़ंक्शन का उपयोग करना चाहते हैं , तो आपको उस बिंदु पर अपनी वस्तु प्राप्त करने का एक तरीका खोजना होगा।

साइड इफेक्ट्स का उपयोग समान कठिनाइयों का निर्माण करता है। यदि आप कुछ कार्यक्षमता के लिए एक साइड इफेक्ट का उपयोग करते हैं, लेकिन इसके कार्यान्वयन को स्वैप करने में सक्षम होना चाहते हैं, तो आपके पास उस निर्भरता को इंजेक्ट करने के अलावा और कोई विकल्प नहीं है।

एक उदाहरण के रूप में एक स्पैमर प्रोग्राम पर विचार करें जो ईमेल पतों के लिए वेब पेजों को स्क्रैप करता है और फिर उन्हें ईमेल करता है। यदि आपके पास DI मानसिकता है, तो अभी आप उन सेवाओं के बारे में सोच रहे हैं जिन्हें आप इंटरफेस के पीछे संलग्न करेंगे, और कौन सी सेवाओं को इंजेक्ट किया जाएगा। मैं उस डिज़ाइन को पाठक के लिए एक अभ्यास के रूप में छोड़ दूँगा। यदि आपके पास एक एफपी मानसिकता है, तो अभी आप कार्यों की सबसे निचली परत के लिए इनपुट और आउटपुट के बारे में सोच रहे हैं, जैसे:

  • एक वेब पेज एड्रेस इनपुट करें, उस पेज के टेक्स्ट को आउटपुट करें।
  • किसी पृष्ठ के पाठ को इनपुट करें, उस पृष्ठ के लिंक की सूची तैयार करें।
  • किसी पृष्ठ के पाठ को इनपुट करें, उस पृष्ठ पर ईमेल पतों की एक सूची तैयार करें।
  • ईमेल पतों की एक सूची इनपुट करें, डुप्लिकेट हटाए गए ईमेल पतों की एक सूची आउटपुट करें।
  • एक ईमेल पता इनपुट करें, उस पते के लिए एक स्पैम ईमेल आउटपुट करें।
  • एक स्पैम ईमेल इनपुट करें, उस ईमेल को भेजने के लिए एसएमटीपी कमांड को आउटपुट करें।

जब आप इनपुट और आउटपुट के संदर्भ में सोचते हैं, तो कोई फ़ंक्शन निर्भरता नहीं होती है, केवल डेटा निर्भरता होती है। यही कारण है कि उन्हें इकाई परीक्षण के लिए इतना आसान बनाता है। आपकी अगली लेयर अप एक फ़ंक्शन के आउटपुट के लिए अगले इनपुट में दिए जाने की व्यवस्था करती है, और आवश्यकतानुसार विभिन्न कार्यान्वयन को आसानी से स्वैप कर सकती है।

एक बहुत ही वास्तविक अर्थ में, कार्यात्मक प्रोग्रामिंग स्वाभाविक रूप से आपको अपने फ़ंक्शन निर्भरताओं को हमेशा उल्टा करने के लिए जोड़ता है, और इसलिए आमतौर पर आपको इस तथ्य के बाद ऐसा करने के लिए कोई विशेष उपाय करने की आवश्यकता नहीं होती है। जब आप करते हैं, तो उच्च-क्रम वाले फ़ंक्शन, क्लोजर और आंशिक अनुप्रयोग जैसे उपकरण कम बॉयलरप्लेट के साथ पूरा करना आसान बनाते हैं।

ध्यान दें कि यह स्वयं निर्भरताएँ नहीं हैं जो समस्याग्रस्त हैं। यह निर्भरताएं हैं जो गलत तरीके से इंगित करती हैं। अगली परत की तरह एक समारोह हो सकता है:

processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses

इस परत के लिए पूरी तरह से ठीक है कि इस तरह निर्भरताएं कठोर-कोडित होती हैं, क्योंकि इसका एकमात्र उद्देश्य निचली परत के कार्यों को एक साथ गोंद करना है। कार्यान्वयन को स्वैप करना एक अलग संरचना बनाने के समान सरल है:

processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses

साइड इफेक्ट्स की कमी से यह आसान पुनर्मूल्यांकन संभव है। निचली परत के कार्य एक दूसरे से पूरी तरह से स्वतंत्र हैं। अगली परत ऊपर चुन सकती है जो processTextवास्तव में कुछ उपयोगकर्ता विन्यास के आधार पर उपयोग की जाती है:

actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText

फिर से, एक मुद्दा नहीं है क्योंकि सभी निर्भरताएं एक तरह से इंगित करती हैं। हम सभी को उसी तरह इंगित करने के लिए कुछ निर्भरताओं को पलटने की आवश्यकता नहीं है, क्योंकि शुद्ध कार्यों ने हमें पहले से ही ऐसा करने के लिए मजबूर किया है।

ध्यान दें कि आप configइसे शीर्ष पर जाँचने के बजाय सबसे निचली परत से गुज़रकर इसे और अधिक युग्मित बना सकते हैं । एफपी आपको ऐसा करने से नहीं रोकता है, लेकिन अगर आप कोशिश करते हैं तो यह बहुत अधिक कष्टप्रद है।


3
"साइड इफेक्ट्स का उपयोग समान कठिनाइयों का निर्माण करता है। यदि आप कुछ कार्यक्षमता के लिए एक साइड इफेक्ट का उपयोग करते हैं, लेकिन इसके कार्यान्वयन को स्वैप करने में सक्षम होना चाहते हैं, तो आपके पास उस निर्भरता को इंजेक्ट करने के अलावा और कोई विकल्प नहीं है।" मुझे नहीं लगता कि साइड इफेक्ट का इससे कोई लेना-देना है। यदि आप हास्केल में कार्यान्वयन को स्वैप करना चाहते हैं, तो आपको अभी भी निर्भरता इंजेक्शन करना होगा । डेसुगर प्रकार की कक्षाएं और आप प्रत्येक फ़ंक्शन के पहले तर्क के रूप में एक इंटरफ़ेस के आसपास से गुजर रहे हैं।
डोभाल

2
इस मामले की जड़ यह है कि लगभग हर भाषा आपको अन्य कोड मॉड्यूल के हार्ड-कोड संदर्भों के लिए मजबूर करती है, इसलिए कार्यान्वयन को स्वैप करने का एकमात्र तरीका हर जगह गतिशील प्रेषण का उपयोग करना है, और फिर आप रन समय पर अपनी निर्भरता को हल करने में फंस जाते हैं। एक मॉड्यूल प्रणाली आपको टाइप-चेकिंग समय पर निर्भरता ग्राफ को व्यक्त करने देती है।
डोभाल

@ डोभाल - आपकी रोचक और सोची-समझी टिप्पणियों के लिए धन्यवाद। हो सकता है कि मैंने आपको गलत समझा हो, लेकिन क्या मैं आपकी टिप्पणियों से यह कहते हुए सही हूं कि अगर मुझे एक डीआई स्टाइल (पारंपरिक सी # अर्थ में) पर प्रोग्रामिंग की एक कार्यात्मक शैली का उपयोग करना था, तो मैं रन-टाइम से जुड़े संभावित डिबगिंग निराशाओं से बचूंगा निर्भरता का संकल्प?
मैट कैसहट

@MatthewPatrickCashatt यह शैली या प्रतिमान की बात नहीं है, बल्कि भाषा की विशेषताओं की है। यदि भाषा प्रथम श्रेणी की चीजों के रूप में मॉड्यूल का समर्थन नहीं करती है, तो आपको कार्यान्वयन को स्वैप करने के लिए कुछ फॉर्म डायनेमिक प्रेषण और निर्भरता इंजेक्शन के लिए करना होगा, क्योंकि निर्भरता को सांख्यिकीय रूप से व्यक्त करने का कोई तरीका नहीं है। इसे थोड़ा अलग करने के लिए, यदि आपका C # प्रोग्राम स्ट्रिंग्स का उपयोग करता है, तो इस पर एक हार्ड-कोडित निर्भरता है System.String। एक मॉड्यूल सिस्टम आपको System.Stringएक चर के साथ बदलने देगा ताकि स्ट्रिंग कार्यान्वयन का विकल्प कठिन-कोडित न हो, लेकिन फिर भी संकलन समय पर हल हो जाए।
डोभाल

8

कार्यात्मकता निर्भरता इंजेक्शन पैटर्न के लिए एक व्यवहार्य विकल्प प्रोग्रामिंग है?

यह मुझे एक अजीब सवाल के रूप में प्रभावित करता है। कार्यात्मक प्रोग्रामिंग दृष्टिकोण काफी हद तक निर्भरता इंजेक्शन के लिए स्पर्शरेखा हैं।

निश्चित रूप से, अपरिवर्तनीय स्थिति होने पर आपको साइड इफेक्ट होने या कार्यों के बीच अंतर्निहित अनुबंध के रूप में वर्ग राज्य का उपयोग करके "धोखा" नहीं करने के लिए धक्का दे सकता है। यह डेटा के पारित होने को और अधिक स्पष्ट करता है, जो मुझे लगता है कि निर्भरता इंजेक्शन का सबसे बुनियादी रूप है। और आस-पास से गुजरने वाले कार्यों की कार्यात्मक प्रोग्रामिंग अवधारणा इसे बहुत आसान बनाती है।

लेकिन यह निर्भरता को दूर नहीं करता है। आपके ऑपरेशन को तब भी सभी डेटा / संचालन की आवश्यकता होती है जब उन्हें आपके राज्य को बदलने योग्य होना चाहिए। और आपको अभी भी किसी तरह से उन निर्भरताओं को प्राप्त करने की आवश्यकता है। इसलिए मैं यह नहीं कहूंगा कि कार्यात्मक प्रोग्रामिंग दृष्टिकोण DI को बिल्कुल बदल देते हैं, इसलिए किसी प्रकार का विकल्प नहीं है।

यदि कुछ भी है, तो उन्होंने आपको केवल यह दिखाया है कि ओओ कोड कितना बुरा बना सकता है जो प्रोग्रामर की तुलना में अंतर्निहित निर्भरता के बारे में शायद ही कभी सोचते हैं।


फिर से बातचीत में योगदान देने के लिए धन्यवाद, टेलस्टाइन। जैसा कि आपने बताया है, मेरा प्रश्न बहुत अच्छी तरह से निर्मित नहीं है (मेरे शब्द), लेकिन यहां प्रतिक्रिया के लिए धन्यवाद, मैं थोड़ा बेहतर समझने लगा हूं कि यह क्या है जो इस सब के बारे में मेरे मस्तिष्क में स्पार्किंग कर रहा है: हम सभी सहमत हैं (मुझे लगता है) कि यूनिट परीक्षण डीआई के साथ एक बुरा सपना हो सकता है। दुर्भाग्य से, डीआई का उपयोग, विशेष रूप से आईओसी कंटेनरों के साथ डिबगिंग दुःस्वप्न का एक नया रूप बना सकता है इस तथ्य के लिए धन्यवाद कि यह रनटाइम पर निर्भरता को हल करता है। डीआई के समान, एफपी इकाई परीक्षण को आसान बनाता है, लेकिन रनटाइम निर्भरता के मुद्दों के बिना।
मैट कैसहट

(ऊपर से जारी)। । यह वैसे भी मेरी वर्तमान समझ है। कृपया मुझे बताएं कि क्या मुझे निशान याद आ रहा है। मुझे यह स्वीकार करने में कोई आपत्ति नहीं है कि मैं यहाँ दिग्गजों के बीच एक मात्र नश्वर हूँ!
बजे मैट कैसहट

@MatthewPatrickCashatt - DI जरूरी नहीं कि रनटाइम निर्भरता के मुद्दे, जो आप ध्यान दें, भयानक हैं।
तेलस्तीन

7

आपके प्रश्न का त्वरित उत्तर है: नहीं

लेकिन जैसा कि दूसरों ने दावा किया है, सवाल दो से शादी करता है, कुछ असंबंधित अवधारणाएं।

आइए इस चरण को चरणबद्ध करें।

DI गैर-कार्यात्मक शैली में परिणाम देता है

फंक्शन प्रोग्रामिंग के मूल में शुद्ध कार्य होते हैं - फ़ंक्शंस जो आउटपुट के लिए इनपुट को मैप करते हैं, इसलिए आपको हमेशा दिए गए इनपुट के लिए समान आउटपुट मिलता है।

DI का आम तौर पर मतलब है कि आपकी इकाई अब शुद्ध नहीं है क्योंकि आउटपुट इंजेक्शन के आधार पर भिन्न हो सकते हैं। उदाहरण के लिए, निम्नलिखित फ़ंक्शन में:

const bookSeats = ( seatCount, getBookedSeatCount ) => { ... }

getBookedSeatCount(एक फ़ंक्शन) एक ही दिए गए इनपुट के लिए अलग-अलग परिणाम देने में भिन्न हो सकते हैं। यह bookSeatsअशुद्ध भी बनाता है ।

इसके लिए अपवाद हैं - आप दो इनपुट एल्गोरिदम में से एक को इंजेक्ट कर सकते हैं जो समान इनपुट-आउटपुट मैपिंग को लागू करते हैं, यद्यपि अन्य एल्गोरिदम का उपयोग करते हुए। लेकिन ये अपवाद हैं।

एक प्रणाली शुद्ध नहीं हो सकती

तथ्य यह है कि एक प्रणाली शुद्ध नहीं हो सकती है समान रूप से नजरअंदाज कर दिया जाता है क्योंकि यह कार्यात्मक प्रोग्रामिंग स्रोतों में मुखर है।

एक प्रणाली के स्पष्ट उदाहरण होने के साथ दुष्प्रभाव होने चाहिए:

  • यूआई
  • डेटाबेस
  • API (क्लाइंट-सर्वर आर्किटेक्चर में)

तो आपके सिस्टम के हिस्से में साइड-इफेक्ट्स शामिल होने चाहिए और यह हिस्सा अच्छी तरह से अनिवार्य शैली, या OO शैली को भी शामिल कर सकता है।

शेल-कोर प्रतिमान

सीमाओं पर गैरी बर्नहार्ट की शानदार बात से शर्तों को उधार लेते हुए , एक अच्छी प्रणाली (या मॉड्यूल) वास्तुकला में दो अन्य शामिल होंगे:

  • कोर
    • शुद्ध कार्य
    • शाखाओं में
    • कोई निर्भरता नहीं
  • खोल
    • प्रभाव (दुष्प्रभाव)
    • कोई शाखा नहीं
    • निर्भरता
    • जरूरी हो सकता है, OO शैली को शामिल करें, आदि।

मुख्य टेकअवे सिस्टम को शुद्ध भाग (कोर) और अशुद्ध भाग (शेल) में 'विभाजित' करना है।

यद्यपि थोड़ा त्रुटिपूर्ण समाधान (और निष्कर्ष) की पेशकश करते हुए, मार्क सेमन का यह लेख बहुत ही अवधारणा का प्रस्ताव करता है। हास्केल कार्यान्वयन विशेष रूप से व्यावहारिक है क्योंकि यह दिखाता है कि यह सभी एफपी का उपयोग करके किया जा सकता है।

DI और FP

अपने आवेदन के थोक शुद्ध है, भले ही रोजगार DI पूरी तरह से उचित है। कुंजी को अशुद्ध शेल के भीतर DI को सीमित करना है।

एक उदाहरण एपीआई स्टब्स होगा - आप उत्पादन में असली एपीआई चाहते हैं, लेकिन परीक्षण में स्टब्स का उपयोग करें। शेल-कोर मॉडल का पालन करने से यहां काफी हद तक मदद मिलेगी।

निष्कर्ष

तो एफपी और डीआई बिल्कुल विकल्प नहीं हैं। आपके सिस्टम में दोनों होने की संभावना है, और सलाह यह है कि सिस्टम के शुद्ध और अशुद्ध भाग के बीच अलगाव सुनिश्चित करें, जहां एफपी और डीआई क्रमशः रहते हैं।


जब आप शेल-कोर प्रतिमान का उल्लेख करते हैं, तो कोई शेल में कोई शाखा कैसे प्राप्त करेगा? मैं कई उदाहरणों के बारे में सोच सकता हूं, जहां किसी एप्लिकेशन को मूल्य के आधार पर एक अशुद्ध चीज या किसी अन्य को करने की आवश्यकता होगी। क्या यह नो-ब्रांचिंग नियम जावा जैसी भाषाओं में लागू है?
जहाराली

@ जर्राहाली कृपया विवरण के लिए गैरी बर्नहार्ट की बात (उत्तर में लिंक) देखें।
इज़्हाकी

एक और रिलेटिव

1

OOP के दृष्टिकोण से कार्यों को एकल-विधि इंटरफेस माना जा सकता है।

इंटरफ़ेस एक फ़ंक्शन की तुलना में एक मजबूत अनुबंध है।

यदि आप एक कार्यात्मक दृष्टिकोण का उपयोग कर रहे हैं और बहुत सारे DI करते हैं तो OOP दृष्टिकोण का उपयोग करने की तुलना में आपको प्रत्येक निर्भरता के लिए अधिक उम्मीदवार मिलेंगे।

void DoStuff(Func<DateTime> getDateTime) {}; //Anything that satisfies the signature can be injected.

बनाम

void DoStuff(IDateTimeProvider dateTimeProvider) {}; //Only types implementing the interface can be injected.

3
इंटरफ़ेस को लागू करने के लिए किसी भी वर्ग को लपेटा जा सकता है ताकि "मजबूत अनुबंध" ज्यादा मजबूत न हो। अधिक महत्वपूर्ण बात यह है कि प्रत्येक फ़ंक्शन को एक अलग प्रकार देना यह बॉर्डरलाइन को फ़ंक्शन संरचना करना असंभव बनाता है।
डोभाल

कार्यात्मक प्रोग्रामिंग का अर्थ यह नहीं है कि "उच्च क्रम कार्यों के साथ प्रोग्रामिंग", यह एक व्यापक अवधारणा को संदर्भित करता है, उच्च क्रम फ़ंक्शन प्रतिमान में उपयोगी केवल एक तकनीक है।
जिमी हॉफ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.