क्या परस्पर विरोधी फ़ंक्शन मापदंडों को संभालने के लिए एक पैटर्न है?


38

हमारे पास एक एपीआई फ़ंक्शन है जो दी गई शुरुआत और समाप्ति तिथियों के आधार पर मासिक राशि में कुल राशि को तोड़ता है।

// JavaScript

function convertToMonths(timePeriod) {
  // ... returns the given time period converted to months
}

function getPaymentBreakdown(total, startDate, endDate) {
  const numMonths = convertToMonths(endDate - startDate);

  return {
    numMonths,
    monthlyPayment: total / numMonths,
  };
}

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

// JavaScript

function addMonths(date, numMonths) {
  // ... returns a new date numMonths after date
}

function getPaymentBreakdown(
  total,
  startDate,
  endDate /* optional */,
  numMonths /* optional */,
  monthlyPayment /* optional */,
) {
  let innerNumMonths;

  if (monthlyPayment) {
    innerNumMonths = total / monthlyPayment;
  } else if (numMonths) {
    innerNumMonths = numMonths;
  } else {
    innerNumMonths = convertToMonths(endDate - startDate);
  }

  return {
    numMonths: innerNumMonths,
    monthlyPayment: total / innerNumMonths,
    endDate: addMonths(startDate, innerNumMonths),
  };
}

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

इसके अतिरिक्त मुझे लगता है कि यह एक बुरी मिसाल कायम करता है और एपीआई के लिए जिम्मेदारियों को जोड़ता है कि इसे खुद से चिंता नहीं करनी चाहिए (यानी एसआरपी का उल्लंघन करना)। मान लीजिए कि अतिरिक्त उपभोक्ता चाहते हैं कि फ़ंक्शन अधिक उपयोग के मामलों का समर्थन करें, जैसे totalकि numMonthsऔर monthlyPaymentमापदंडों से गणना करना। यह फ़ंक्शन समय के साथ अधिक से अधिक जटिल हो जाएगा।

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

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


79
"हाल ही में, इस एपीआई के लिए एक उपभोक्ता अंतिम तिथि के बजाय महीनों की संख्या प्रदान करना चाहता था" - यह एक तुच्छ अनुरोध है। वे अपने महीनों के # महीने को एक पंक्ति में दो बार कोड में एक उचित समाप्ति दिनांक में बदल सकते हैं।
ग्राहम

12
यह एक ध्वज-विरोधी पैटर्न की तरह दिखता है, और मैं कई कार्यों में विभाजन की भी सिफारिश
करूंगा

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

एक मामूली स्पर्शरेखा पर, आप यह सोचना चाहते हैं कि जहां monthlyPaymentदिया गया है उस मामले को कैसे संभालना है, लेकिन totalइसका एक पूर्णांक एकाधिक नहीं है। और यह भी कि कैसे संभव फ़्लोटिंग पॉइंट राउंडऑफ़ त्रुटियों से निपटने के लिए यदि मान पूर्णांक होने की गारंटी नहीं है (जैसे इसके साथ प्रयास करें total = 0.3और monthlyPayment = 0.1)।
इल्मरी करोनन

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

जवाबों:


99

कार्यान्वयन को देखकर, यह मुझे प्रतीत होता है कि आपको वास्तव में यहां क्या चाहिए, एक के बजाय 3 अलग-अलग कार्य हैं:

मूल एक:

function getPaymentBreakdown(total, startDate, endDate) 

अंतिम तिथि के बजाय महीनों की संख्या प्रदान करने वाला:

function getPaymentBreakdownByNoOfMonths(total, startDate, noOfMonths) 

और मासिक भुगतान प्रदान करने और अंतिम तिथि की गणना करने वाला:

function getPaymentBreakdownByMonthlyPayment(total, startDate, monthlyPayment) 

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

ध्यान दें कि विभिन्न कार्यों का मतलब यह नहीं है कि आपको किसी भी तर्क की नकल करनी है - आंतरिक रूप से, यदि ये फ़ंक्शन एक सामान्य एल्गोरिथ्म साझा करते हैं, तो इसे "निजी" फ़ंक्शन पर वापस ले जाना चाहिए।

इस तरह से परिदृश्यों को संभालने के लिए एक सामान्य पैटर्न है

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


24
वास्तव में कोड का "आम" कार्यान्वयन बस हो सकता है getPaymentBreakdown(या वास्तव में उन 3 में से कोई एक) और अन्य दो कार्य केवल तर्कों को परिवर्तित करते हैं और कहते हैं कि। एक निजी फ़ंक्शन क्यों जोड़ें जो इन 3 में से किसी एक की सही प्रतिलिपि है?
जियाकोमो अल्जेटा

@GiacomoAlzetta: यह संभव है। लेकिन मुझे पूरा यकीन है कि कार्यान्वयन एक आम फ़ंक्शन प्रदान करके सरल हो जाएगा जिसमें ओपीएस फ़ंक्शन का केवल "रिटर्न" भाग शामिल है, और सार्वजनिक 3 फ़ंक्शन को इस फ़ंक्शन को मापदंडों के साथ कॉल करने दें innerNumMonths, totalऔर startDate। 5 मापदंडों के साथ एक ओवरकम्प्लिकेटेड फ़ंक्शन क्यों रखें, जहां 3 लगभग वैकल्पिक हैं (एक को सेट करने के अलावा), जब 3-पैरामीटर फ़ंक्शन भी काम करेगा?
डॉक्टर ब्राउन

3
मेरे कहने का मतलब यह नहीं है कि "5 तर्क रखें"। मैं सिर्फ यह कह रहा हूं कि जब आपके पास कुछ सामान्य तर्क हैं तो इस तर्क को निजी होने की आवश्यकता नहीं है । इस स्थिति में सभी 3 कार्यों को स्टार्ट-एंड डेट्स के मापदंडों को बदलने के लिए बस फिर से शुरू किया जा सकता है, इसलिए आप सार्वजनिक getPaymentBreakdown(total, startDate, endDate)फ़ंक्शन को सामान्य कार्यान्वयन के रूप में उपयोग कर सकते हैं , अन्य उपकरण बस उपयुक्त कुल / प्रारंभ / समाप्ति तिथियों की गणना करेंगे और इसे कॉल करेंगे।
जियाकोमो अल्जेटा

@GiacomoAlzetta: ठीक है, गलतफहमी थी, मुझे लगा कि आप getPaymentBreakdownप्रश्न में दूसरे कार्यान्वयन के बारे में बात कर रहे हैं ।
डॉक ब्राउन

जहाँ तक आप मूल विधि का एक नया संस्करण जोड़ना चाहते हैं, जिसे स्पष्ट रूप से 'getPaymentBreakdownByStartAndEnd' कहा जाता है और यदि आप इन सभी की आपूर्ति करना चाहते हैं, तो मूल विधि को हटा देना चाहिए।
एरिक

20

इसके अतिरिक्त मुझे लगता है कि यह एक बुरी मिसाल कायम करता है और एपीआई के लिए जिम्मेदारियों को जोड़ता है कि इसे खुद से चिंता नहीं करनी चाहिए (यानी एसआरपी का उल्लंघन करना)। मान लीजिए कि अतिरिक्त उपभोक्ता चाहते हैं कि फ़ंक्शन अधिक उपयोग के मामलों का समर्थन करें, जैसे totalकि numMonthsऔर monthlyPaymentमापदंडों से गणना करना। यह फ़ंक्शन समय के साथ अधिक से अधिक जटिल हो जाएगा।

आप बिल्कुल सही हैं।

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

यह या तो आदर्श नहीं है, क्योंकि कॉलर कोड असंबंधित बॉयलर प्लेट के साथ प्रदूषित होगा।

वैकल्पिक रूप से, क्या इस तरह से परिदृश्यों को संभालने के लिए एक सामान्य पैटर्न है?

एक नए प्रकार का परिचय दें, जैसे DateInterval। जो भी निर्माण करने वालों को समझ में आता है (प्रारंभ दिनांक + समाप्ति दिनांक, प्रारंभ दिनांक + संख्या महीने, जो भी हो) जोड़ें। इसे पूरे सिस्टम में दिनांक / समय के अंतराल को व्यक्त करने के लिए सामान्य-मुद्रा के प्रकारों के रूप में अपनाएं।


3
@DocBrown हां। ऐसे मामलों में (रूबी, पायथन, जेएस), यह सिर्फ स्थिर / वर्ग विधियों का उपयोग करने के लिए प्रथागत है। लेकिन यह एक कार्यान्वयन विवरण है, जो मुझे नहीं लगता कि मेरे उत्तर के बिंदु ("एक प्रकार का उपयोग करें") के लिए विशेष रूप से प्रासंगिक है।
अलेक्जेंडर

2
और यह विचार दु: खद रूप से तीसरी आवश्यकता के साथ अपनी सीमा तक पहुंचता है: प्रारंभ दिनांक, कुल भुगतान और एक मासिक भुगतान - और फ़ंक्शन मनी पैरामीटर से DateInterval की गणना करेगा - और आपको मौद्रिक मात्रा को अपनी तिथि सीमा में नहीं रखना चाहिए ...
फाल्को

3
@DocBrown "केवल मौजूदा फ़ंक्शन से टाइप के निर्माता को समस्या को शिफ्ट करता है" हां, यह समय कोड डाल रहा है जहां समय कोड जाना चाहिए, ताकि मनी कोड हो सकता है जहां मनी कोड जाना चाहिए। यह सरल एसआरपी है, इसलिए मुझे यकीन नहीं है कि जब आप इसे "केवल" कहते हैं तो आपको समस्या का सामना करना पड़ता है। यही सभी कार्य करते हैं। वे कोड को दूर नहीं बनाते हैं, वे इसे अधिक उपयुक्त स्थानों में स्थानांतरित करते हैं। आपका क्या मुद्दा है? "लेकिन मेरी बधाई, कम से कम 5 upvoters ने चारा लिया" मुझे लगता है कि आप (इरादा) की तुलना में यह बहुत अधिक गधे लगता है।
अलेक्जेंडर

@ फाल्को यह मेरे लिए एक नई विधि की तरह लगता है (इस भुगतान कैलकुलेटर वर्ग पर, नहीं DateInterval):calculatePayPeriod(startData, totalPayment, monthlyPayment)
अलेक्जेंडर

7

कभी-कभी धाराप्रवाह-भाव इस पर मदद करते हैं:

let payment1 = forTotalAmount(1234)
                  .breakIntoPayments()
                  .byPeriod(months(2));

let payment2 = forTotalAmount(1234)
                  .breakIntoPayments()
                  .byDateRange(saleStart, saleEnd);

let monthsDue = forTotalAmount(1234)
                  .calculatePeriod()
                  .withPaymentsOf(12.34)
                  .monthly();

डिजाइन करने के लिए पर्याप्त समय को देखते हुए, आप एक ठोस एपीआई के साथ आ सकते हैं जो एक डोमेन-विशिष्ट-भाषा के समान कार्य करता है।

दूसरा बड़ा फायदा यह है कि स्वत: पूर्णता के साथ आईडीई एपीआई प्रलेखन को पढ़ने के लिए लगभग अपरिवर्तनीय बनाता है, क्योंकि यह स्वयं-खोज योग्य क्षमताओं के कारण सहज है।

इस विषय पर https://nikas.praninskas.com/javascript/2015/04/26/fluent-javascript/ या https://github.com/nikaspran/fluent.js जैसे संसाधन हैं।

उदाहरण (पहले संसाधन लिंक से लिया गया):

let insert = (value) => ({into: (array) => ({after: (afterValue) => {
  array.splice(array.indexOf(afterValue) + 1, 0, value);
  return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]

8
धाराप्रवाह इंटरफ़ेस अपने आप में किसी विशेष कार्य आसान या कठिन नहीं है। यह बिल्डर पैटर्न की तरह लगता है।
वीएलएजी

8
यदि आप गलती से कॉल को रोकने की जरूरत है, तो कार्यान्वयन बल्कि जटिल होगाforTotalAmount(1234).breakIntoPayments().byPeriod(2).monthly().withPaymentsOf(12.34).byDateRange(saleStart, saleEnd);
बर्गी

4
यदि डेवलपर्स वास्तव में अपने पैरों पर शूट करना चाहते हैं, तो वहाँ आसान तरीके @Bergi हैं। फिर भी, आपके द्वारा रखा गया उदाहरण इससे कहीं अधिक पठनीय हैforTotalAmountAndBreakIntoPaymentsByPeriodThenMonthlyWithPaymentsOfButByDateRange(1234, 2, 12.34, saleStart, saleEnd);
डैनियलकुआड्रा

5
@DanielCuadra मैं जिस बिंदु को बनाने की कोशिश कर रहा था वह यह है कि आपका उत्तर वास्तव में 3 परस्पर अनन्य पैरामीटर होने की ओपी समस्या को हल नहीं करता है। बिल्डर पैटर्न का उपयोग करने से कॉल अधिक पठनीय हो सकती है (और उपयोगकर्ता को यह ध्यान देने की संभावना बढ़ जाती है कि इसका कोई मतलब नहीं है), लेकिन अकेले बिल्डर पैटर्न का उपयोग करने से उन्हें अभी भी एक बार में 3 मान पास करने से नहीं रोका जा सकता है।
बरगी

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

2

अन्य भाषाओं में, आप नामित मापदंडों का उपयोग करेंगे । यह जावास्क्रिप्ट में अनुकरण किया जा सकता है:

function getPaymentBreakdown(total, startDate, durationSpec) { ... }

getPaymentBreakdown(100, today, {endDate: whatever});
getPaymentBreakdown(100, today, {noOfMonths: 4});
getPaymentBreakdown(100, today, {monthlyPayment: 20});

6
नीचे दिए गए बिल्डर पैटर्न की तरह, यह कॉल को अधिक पठनीय बनाता है (और उपयोगकर्ता को यह सूचित करने की संभावना को उठाता है कि इसका कोई मतलब नहीं है), लेकिन मापदंडों का नामकरण अभी भी उपयोगकर्ता को एक बार में 3 मानों को पारित करने से नहीं रोकता है - जैसे getPaymentBreakdown(100, today, {endDate: whatever, noOfMonths: 4, monthlyPayment: 20})
बरगी

1
:इसके बजाय नहीं होना चाहिए =?
बरमार

मुझे लगता है कि आप जाँच सकते हैं कि केवल एक पैरामीटर गैर-अशक्त है (या शब्दकोश में नहीं है)।
मतीन उल्हाक

1
@Bergi - वाक्यविन्यास ही उपयोगकर्ताओं को निरर्थक मापदंडों को पारित करने से नहीं रोकता है, लेकिन आप बस कुछ सत्यापन कर सकते हैं और त्रुटियों को फेंक सकते हैं
स्लीपबेटमैन

@ बर्गी मैं किसी भी तरह से एक जावास्क्रिप्ट विशेषज्ञ नहीं हूं, लेकिन मुझे लगता है कि ES6 में विनाशकारी असाइनमेंट यहां मदद कर सकता है, हालांकि मैं इसके साथ ज्ञान पर बहुत हल्का हूं।
ग्रेगरी करी

1

एक विकल्प के रूप में आप महीने की संख्या को निर्दिष्ट करने और अपने कार्य से बाहर जाने की जिम्मेदारी को भी तोड़ सकते हैं:

getPaymentBreakdown(420, numberOfMonths(3))
getPaymentBreakdown(420, dateRage(a, b))
getPaymentBreakdown(420, paymentAmount(350))

और getpaymentBreakdown को एक वस्तु प्राप्त होगी जो महीनों की आधार संख्या प्रदान करेगी

उदाहरण के लिए एक समारोह में लौटने के लिए वे उच्चतर क्रम समारोह करेंगे।

function numberOfMonths(months) {
  return {months: (total) => months};
}

function dateRange(startDate, endDate) {
  return {months: (total) => convertToMonths(endDate - startDate)}
}

function monthlyPayment(amount) {
  return {months: (total) => total / amount}
}


function getPaymentBreakdown(total, {months}) {
  const numMonths= months(total);
  return {
    numMonths, 
    monthlyPayment: total / numMonths,
    endDate: addMonths(startDate, numMonths)
  };
}

totalऔर startDateमापदंडों का क्या हुआ ?
बर्गी

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


0

और अगर आप भेदभाव रहित यूनियनों / बीजीय डेटा प्रकारों के साथ एक प्रणाली के साथ काम कर रहे थे, तो आप इसे इस रूप में पारित कर सकते हैं, जैसे कि, ए TimePeriodSpecification

type TimePeriodSpecification =
    | DateRange of startDate : DateTime * endDate : DateTime
    | MonthCount of startDate : DateTime * monthCount : int
    | MonthlyPayment of startDate : DateTime * monthlyAmount : float

और फिर कोई भी समस्या नहीं होगी जहां आप वास्तव में एक को लागू करने में विफल हो सकते हैं।


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