Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

डिकूपिंग रूबी:डेलिगेशन बनाम डिपेंडेंसी इंजेक्शन

ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग . में , एक वस्तु अक्सर कार्य करने के लिए दूसरी वस्तु पर निर्भर करती है।

उदाहरण के लिए, अगर मैं वित्त रिपोर्ट चलाने के लिए एक साधारण वर्ग बनाता हूं:

class FinanceReport
  def net_income
    FinanceApi.gross_income - FinanceApi.total_costs
  end
end

हम कह सकते हैं कि FinanceReport निर्भर करता है FinanceApi , जिसका उपयोग वह बाहरी भुगतान संसाधक से जानकारी प्राप्त करने के लिए करता है।

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

निर्भरता इंजेक्शन के साथ, हम स्पष्ट रूप से FinanceApi . का उल्लेख नहीं करते हैं FinanceReport . के अंदर . इसके बजाय, हम इसे एक तर्क के रूप में पास करते हैं। हम इंजेक्ट यह।

निर्भरता इंजेक्शन का उपयोग करके, हमारी कक्षा बन जाती है:

class FinanceReport
  def net_income(financials)
    financials.gross_income - financials.total_costs
  end
end

अब हमारी कक्षा को यह जानकारी नहीं है कि FinanceApi वस्तु भी मौजूद है! हम किसी भी वस्तु को पास कर सकते हैं इसके लिए जब तक यह gross_income . को लागू करता है और total_costs

इसके कई लाभ हैं:

  • हमारा कोड अब कम "युग्मित" से FinanceApi . हो गया है ।
  • हमें FinanceApi . का उपयोग करने के लिए बाध्य किया गया है एक सार्वजनिक इंटरफ़ेस के माध्यम से।
  • अब हम अपने परीक्षणों में एक नकली या स्टब ऑब्जेक्ट में पास कर सकते हैं ताकि हमें वास्तविक एपीआई को हिट न करना पड़े।

अधिकांश डेवलपर निर्भरता इंजेक्शन . पर विचार करते हैं सामान्य तौर पर एक अच्छी बात बनने के लिए (मैं भी!) हालांकि, जैसा कि सभी तकनीकों के साथ होता है, इसमें समझौता होता है।

हमारा कोड अब थोड़ा अधिक अपारदर्शी है। जब हमने स्पष्ट रूप से FinanceApi . का उपयोग किया था , यह स्पष्ट था कि हमारे मूल्य कहां से आ रहे थे। डिपेंडेंसी इंजेक्शन को शामिल करने वाले कोड में यह बिल्कुल स्पष्ट नहीं है।

अगर कॉल्स अन्यथा self . पर चली जातीं , तो हमने कोड को और अधिक क्रियात्मक बना दिया है। ऑब्जेक्ट-ओरिएंटेड "किसी ऑब्जेक्ट को एक संदेश भेजें और इसे कार्य करने दें" प्रतिमान का उपयोग करने के बजाय, हम खुद को एक अधिक कार्यात्मक "इनपुट -> आउटपुट" प्रतिमान की ओर बढ़ते हुए पाते हैं।

यह आखिरी मामला है (कॉल को पुनर्निर्देशित करना जो self . पर जाएगा ) जिसे मैं आज देखना चाहता हूं। मैं इन स्थितियों के लिए निर्भरता इंजेक्शन के लिए एक संभावित विकल्प प्रस्तुत करना चाहता हूं:आधार वर्ग को गतिशील रूप से बदलें (थोड़े)।

समस्या को हल करना है

आइए एक पल का बैक अप लें और उस समस्या से शुरू करें जिसने मुझे इस पथ से शुरू करने के लिए प्रेरित किया:पीडीएफ रिपोर्ट।

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

हम आदरणीय prawn का उपयोग कर रहे हैं मणि इन पीडीएफ़ को बनाने के लिए, प्रत्येक रिपोर्ट की अपनी रूबी ऑब्जेक्ट होने के साथ Prawn::Document से उप-वर्गीकृत किया गया है ।

कुछ इस तरह:

class CostReport < Prawn::Document
  def initialize(...)
    ...
  end

  def render
    text "Cost Report"
    move_down 20
    ...
  end

अब तक सब ठीक है. लेकिन यह बात है:ग्राहक एक "अवलोकन" रिपोर्ट चाहता है जिसमें इन सभी अन्य रिपोर्टों के अंश शामिल हों

समाधान 1:निर्भरता इंजेक्शन

जैसा कि पहले उल्लेख किया गया है, इस तरह की समस्या का एक सामान्य समाधान डिपेंडेंसी इंजेक्शन का उपयोग करने के लिए कोड को रिफलेक्टर करना है। यानी, इन सभी रिपोर्टों को self . पर कॉल करने के तरीकों के बजाय , इसके बजाय हम अपने PDF दस्तावेज़ को तर्क के रूप में पास करेंगे।

इससे हमें कुछ और मिलेगा:

class CostReport < Prawn::Document
...
  def title(pdf = self)
    pdf.text "Cost Report"
    pdf.move_down 20
    ...
  end
end

यह काम करता है, लेकिन यहां कुछ ओवरहेड है। एक बात के लिए, हर एक ड्राइंग विधि को अब pdf लेना होगा तर्क, और हर एक कॉल prawn अब इस pdf से गुजरना होगा तर्क।

डिपेंडेंसी इंजेक्शन के कुछ लाभ हैं:यह हमें हमारे सिस्टम में डिकूप्ड घटकों की ओर धकेलता है और यूनिट परीक्षण को आसान बनाने के लिए हमें मॉक या स्टब्स में पास करने की अनुमति देता है।

हालाँकि, हम अपने परिदृश्य में इन लाभों का प्रतिफल प्राप्त नहीं कर रहे हैं। हम पहले से ही दृढ़ता से हैं prawn . के साथ मिलकर एपीआई, इसलिए एक अलग पीडीएफ लाइब्रेरी में बदलने के लिए लगभग निश्चित रूप से कोड के संपूर्ण पुनर्लेखन की आवश्यकता होगी।

परीक्षण भी यहां एक बड़ी चिंता नहीं है, क्योंकि हमारे मामले में स्वचालित परीक्षणों के साथ उत्पन्न पीडीएफ रिपोर्ट का परीक्षण करना सार्थक होने के लिए बहुत बोझिल है।

तो निर्भरता इंजेक्शन हमें वह व्यवहार देता है जो हम चाहते हैं लेकिन हमारे लिए न्यूनतम लाभ के साथ अतिरिक्त ओवरहेड भी पेश करते हैं। आइए एक और विकल्प देखें।

समाधान 2:प्रतिनिधिमंडल

रूबी की मानक लाइब्रेरी हमें SimpleDelegator प्रदान करती है डेकोरेटर पैटर्न को लागू करने का एक आसान तरीका के रूप में। आप अपने ऑब्जेक्ट में कंस्ट्रक्टर को पास करते हैं, और फिर प्रतिनिधि को कोई भी विधि कॉल आपके ऑब्जेक्ट पर अग्रेषित कर दी जाती है।

SimpleDelegator . का उपयोग करना , हम एक आधार रिपोर्ट वर्ग बना सकते हैं जो prawn . के आसपास लपेटता है ।

class PrawnWrapper < SimpleDelegator
  def initialize(document: nil)
    document ||= Prawn::Document.new(...)
    super(document)
  end
end

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

class OverviewReport < PrawnWrapper
  ...
  def render
    sales = SaleReport.new(..., document: self)
    sales.sales_table
    costs = CostReport.new(..., document: self)
    costs.costs_pie_chart
    ...
  end
end

यहां SaleReport#sales_table और CostReport#costs_pie_chart अपरिवर्तित रहते हैं, लेकिन उनकी कॉल prawn (उदा., text(...) , move_down 20 , आदि) को अब OverviewReport . पर अग्रेषित किया जा रहा है SimpleDelegator . के माध्यम से हमने बनाया।

व्यवहार के संदर्भ में, हमने अनिवार्य रूप से इसे SalesReport . जैसा बना दिया है अब OverviewReport . का उपवर्ग है . हमारे मामले में, इसका मतलब है कि prawn के लिए सभी कॉल्स का एपीआई अब SalesReport -> OverviewReport -> Prawn::Document पर जाएं ।

SimpleDelegator कैसे काम करता है

जिस तरह से SimpleDelegator हुड के तहत काम मूल रूप से रूबी के method_missing . का उपयोग करना है किसी अन्य ऑब्जेक्ट को कॉल अग्रेषित करने की कार्यक्षमता।

तो SimpleDelegator (या इसका एक उपवर्ग) एक विधि कॉल प्राप्त करता है। अगर यह उस विधि को लागू करता है, तो बढ़िया; यह किसी अन्य वस्तु की तरह ही इसे निष्पादित करेगा। हालांकि , अगर उस विधि को परिभाषित नहीं किया गया है, तो यह method_missing . को हिट करेगा . method_missing फिर call . करने का प्रयास करेंगे इसके निर्माता को दी गई वस्तु पर वह विधि।

एक साधारण उदाहरण:

require 'simple_delegator'
class Thing
  def one
    'one'
  end
  def two
    'two'
  end
end

class ThingDecorator < SimpleDelegator
  def two
    'three!'
  end
end

ThingDecorator.new(Thing.new).one #=> "one"
ThingDecorator.new(Thing.new).two #=> "three!"

SimpleDelegator . को उपवर्गित करके हमारे अपने ThingDecorator . के साथ क्लास यहाँ, हम कुछ विधियों को अधिलेखित कर सकते हैं और दूसरों को डिफ़ॉल्ट Thing . के माध्यम से जाने दे सकते हैं वस्तु।

ऊपर दिया गया तुच्छ उदाहरण वास्तव में SimpleDelegator नहीं करता है न्याय, यद्यपि। आप इस कोड को देख सकते हैं और मुझसे बहुत अच्छी तरह कह सकते हैं, "Thing उपवर्ग नहीं करता है" मुझे वही परिणाम दो?”

हाँ, हाँ करता है। लेकिन यहाँ मुख्य अंतर है:SimpleDelegator वह वस्तु लेता है जिसे वह अपने निर्माता में एक तर्क के रूप में सौंपेगा। इसका मतलब है कि हम रनटाइम पर अलग-अलग ऑब्जेक्ट में पास कर सकते हैं

यह वही है जो कॉल को prawn पर पुनर्निर्देशित करने की अनुमति देता है ऊपर समाधान 2 में वस्तु। अगर हम एक ही रिपोर्ट कहते हैं, prawn कॉल कंस्ट्रक्टर में बनाए गए एक नए दस्तावेज़ पर जाते हैं। हालांकि, ओवरव्यू रिपोर्ट इसे बदल सकती है ताकि prawn को कॉल किया जा सके इसकी . को अग्रेषित किए जाते हैं दस्तावेज़।

निष्कर्ष

निर्भरता इंजेक्शन शायद अधिकांश . के लिए सबसे अच्छा समाधान है डिकूपिंग समस्याएं अधिकांश उस समय के।

हालांकि, सभी तकनीकों के साथ, ट्रेड-ऑफ़ हैं। मेरे मामले में, मुझे नहीं लगता था कि DI द्वारा पेश किया गया ओवरहेड इसके द्वारा प्रदान किए गए लाभों के लायक था, इसलिए मैंने दूसरा समाधान खोजा।

रूबी में सभी चीजों की तरह, हमेशा एक और तरीका होता है . मैं इस समाधान के लिए अक्सर नहीं पहुँचता, लेकिन इन स्थितियों के लिए यह निश्चित रूप से आपके रूबी टूलबेल्ट के लिए एक अच्छा अतिरिक्त है।


  1. रूबी में बिटवाइज़ हैक्स

    संभावना है कि आपको आमतौर पर अपने दिन के काम में थोड़ा सा गणित करने की आवश्यकता नहीं होती है। रूबी के बिटवाइज़ AND और OR ऑपरेटर्स ( &और | ) का उपयोग संभवतः दुर्घटनावश उद्देश्य से अधिक किया जाता है। किसने गलती से टाइप नहीं किया और कब उनका मतलब &&था? लेकिन अगर आप सी या असेंबलर, या मेरे मामले में टर्बो

  1. रूबी में लैम्ब्डा का उपयोग करना

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

  1. रूबी में 9 नई सुविधाएँ 2.6

    रूबी का एक नया संस्करण नई सुविधाओं और प्रदर्शन में सुधार के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज रूबी 2.5 और पुराने संस्करण पहले से ही अंतहीन श्रेणी के एक रूप का समर्थन करते हैं (Float::INFINITY के साथ) ), लेकिन रूबी 2.6 इसे अगले स्तर पर ले