एपीआई डिजाइन में, तदर्थ बहुरूपता का उपयोग / बचने के लिए कब?


14

मुकदमा, एक JavaScript लाइब्रेरी डिजाइन Magician.js। इसकी लिंचपिन एक फ़ंक्शन है जो Rabbitपास किए गए तर्क से बाहर खींचती है ।

वह जानती है कि उसके उपयोगकर्ता खरगोश को एक String, एक Number, एक Function, शायद यहां तक ​​कि बाहर खींच सकते हैं HTMLElement। इस बात को ध्यान में रखते हुए, वह अपनी एपीआई को इस तरह डिजाइन कर सकती है:

सख्त इंटरफ़ेस

Magician.pullRabbitOutOfString = function(str) //...
Magician.pullRabbitOutOfHTMLElement = function(htmlEl) //...

उपरोक्त उदाहरण में प्रत्येक फ़ंक्शन जानता होगा कि फ़ंक्शन नाम / पैरामीटर नाम में निर्दिष्ट प्रकार के तर्क को कैसे संभालना है।

या, वह इसे इस तरह डिजाइन कर सकती है:

"तदर्थ" इंटरफ़ेस

Magician.pullRabbit = function(anything) //...

pullRabbitविभिन्न प्रकार के अपेक्षित प्रकारों के लिए खाता रखना होगा जो anythingतर्क हो सकते हैं, साथ ही साथ (निश्चित रूप से) एक अप्रत्याशित प्रकार:

Magician.pullRabbit = function(anything) {
  if (anything === undefined) {
    return new Rabbit(); // out of thin air
  } else if (isString(anything)) {
    // more
  } else if (isNumber(anything)) {
    // more
  }
  // etc.
};

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

इस प्रश्न के उत्तर के लिए , मैं या तो दृष्टिकोण के लिए विशिष्ट पेशेवरों और विपक्षों को देखना चाहता हूं (या पूरी तरह से एक अलग दृष्टिकोण के लिए, अगर न तो आदर्श है), कि सू को पता होना चाहिए कि उसे पुस्तकालय के एपीआई को डिजाइन करते समय कौन सा दृष्टिकोण लेना चाहिए।


जवाबों:


7

कुछ पेशेवरों और विपक्ष

पॉलीमॉर्फिक के लिए पेशेवरों:

  • एक छोटा बहुरूपी इंटरफ़ेस पढ़ने में आसान है। मुझे केवल एक तरीका याद रखना है।
  • यह जिस तरह से भाषा का उपयोग करने के लिए है - डक टाइपिंग के साथ जाता है।
  • यदि यह स्पष्ट है कि मैं किन वस्तुओं को एक खरगोश से बाहर निकालना चाहता हूं, तो वैसे भी अस्पष्टता नहीं होनी चाहिए।
  • जावा जैसी स्थिर भाषाओं में भी बहुत प्रकार की जाँच करना बुरा माना जाता है, जहाँ वस्तु के प्रकार के लिए बहुत प्रकार की जाँचें बदसूरत कोड बनाती हैं, तो क्या जादूगर को वास्तव में उन वस्तुओं के प्रकार के बीच अंतर करने की आवश्यकता है जिन्हें वह खरगोश से खींच रहा है। ?

तदर्थ के लिए पेशेवरों:

  • यह कम स्पष्ट है, क्या मैं एक स्ट्रिंग को Catउदाहरण से बाहर खींच सकता हूं ? क्या यह सिर्फ काम करेगा? यदि नहीं, तो व्यवहार क्या है? यदि मैं यहां टाइप को सीमित नहीं करता हूं, तो मुझे दस्तावेज़ीकरण में, या उन परीक्षणों में करना होगा जो एक बदतर अनुबंध कर सकते हैं।
  • आपके पास एक जगह पर एक खरगोश को खींचने का सारा काम है, जादूगर (कुछ इस पर विचार कर सकते हैं)
  • आधुनिक जेएस ऑप्टिमाइज़र मोनोमोर्फिक (केवल एक प्रकार पर काम करता है) और बहुरूपी कार्यों के बीच अंतर करते हैं। वे monomorphic वाले अनुकूलन करने के लिए कैसे पता ज्यादा बेहतर तो pullRabbitOutOfStringसंस्करण की संभावना है होना करने के लिए बहुत तेजी से वी 8 की तरह इंजन में। अधिक जानकारी के लिए यह वीडियो देखें। संपादित करें: मैंने खुद को एक पूर्ण लिखा था, यह पता चला है कि व्यवहार में, यह हमेशा ऐसा नहीं होता है

कुछ वैकल्पिक उपाय:

मेरी राय में, इस तरह का डिज़ाइन बहुत 'जावा-स्क्रिप्टी' से शुरू नहीं होता है। जावास्क्रिप्ट एक अलग भाषा है जिसमें C #, जावा या पायथन जैसी भाषाओं के अलग-अलग मुहावरे हैं। ये मुहावरे डेवलपर्स के वर्षों में उत्पन्न होते हैं जो भाषा के कमजोर और मजबूत हिस्सों को समझने की कोशिश कर रहे हैं, मैं क्या करूँगा इन मुहावरों के साथ रहने की कोशिश करता हूं।

मेरे विचार से दो अच्छे समाधान हैं:

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

समाधान 1: वस्तुओं को ऊपर उठाना

इस समस्या का एक सामान्य समाधान, उन वस्तुओं को 'उन्नत' करना है, जिनमें खरगोशों को बाहर निकालने की क्षमता होती है।

यही है, एक फ़ंक्शन है जो किसी प्रकार की वस्तु लेता है, और इसके लिए एक टोपी से बाहर खींचता है। कुछ इस तरह:

function makePullable(obj){
   obj.pullOfHat = function(){
       return new Rabbit(obj.toString());
   }
}

मैं makePullableअन्य वस्तुओं के लिए ऐसे कार्य कर सकता हूं, मैं एक बना सकता हूं makePullableString, आदि मैं प्रत्येक प्रकार पर रूपांतरण को परिभाषित कर रहा हूं। हालाँकि, मैंने अपनी वस्तुओं को ऊँचा उठाने के बाद, मुझे उन्हें सामान्य तरीके से इस्तेमाल करने का कोई प्रकार नहीं है। जावास्क्रिप्ट में एक इंटरफ़ेस एक बतख टाइपिंग द्वारा निर्धारित किया जाता है, अगर इसकी एक pullOfHatविधि है तो मैं इसे जादूगर की विधि के साथ खींच सकता हूं ।

तब जादूगर कर सकता था:

Magician.pullRabbit = function(pullable) {
    var rabbit = obj.pullOfHat();
    return {rabbit:rabbit,text:"Tada, I pulled a rabbit out of "+pullable};
}

ऑब्जेक्ट्स को ऊपर उठाना, कुछ प्रकार के मिक्सिन पैटर्न का उपयोग करना ऐसा लगता है जैसे कि अधिक जेएस चीज़। (ध्यान दें कि यह भाषा में मूल्य प्रकारों के साथ समस्याग्रस्त है जो स्ट्रिंग, संख्या, अशक्त, अपरिभाषित और बूलियन हैं, लेकिन वे सभी बॉक्स-सक्षम हैं)

यहाँ एक उदाहरण है कि ऐसा कोड कैसा दिख सकता है

समाधान 2: रणनीति पैटर्न

जब StackOverflow में JS चैट रूम में इस प्रश्न पर चर्चा करते हुए मेरे मित्र फिनोमनोमनोमिनल ने स्ट्रैटेजी पैटर्न के उपयोग का सुझाव दिया ।

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

यहाँ यह कैसे कॉफीस्क्रिप्ट में लग सकता है:

class Magician
  constructor: ()-> # A new Magician can't pull anything
     @pullFunctions = {}

  pullRabbit: (obj) -> # Pull a rabbit, handler based on type
    func = pullFunctions[obj.constructor.name]
    if func? then func(obj) else "Don't know how to pull that out of my hat!"

  learnToPull: (obj, handler) -> # Learns to pull a rabbit out of a type
    pullFunctions[obj.constructor.name] = handler

आप यहाँ समान JS कोड देख सकते हैं ।

इस तरह, आप दोनों दुनिया से लाभान्वित होते हैं, खींचने की क्रिया को या तो वस्तुओं, या जादूगर से कड़ाई से जोड़ा नहीं जाता है और मुझे लगता है कि यह एक बहुत अच्छा समाधान है।

उपयोग कुछ इस तरह होगा:

var m = new Magician();//create a new Magician
//Teach the Magician
m.learnToPull("",function(){
   return "Pulled a rabbit out of a string";
});
m.learnToPull({},function(){
   return "Pulled a rabbit out of a Object";
});

m.pullRabbit(" Str");


2
मैंने +10 इसे बहुत ही गहन उत्तर के लिए दिया, जिसमें से मैंने बहुत कुछ सीखा, लेकिन, एसई के नियमों के अनुसार, आपको +1 के लिए समझौता करना होगा ... :-)
मार्जन वेनमा

@MarjanVenema अन्य उत्तर भी अच्छे हैं, उन्हें भी पढ़ना सुनिश्चित करें। मुझे खुशी है कि आपने इसका आनंद लिया। बेझिझक रुकें और अधिक डिजाइन प्रश्न पूछें।
बेंजामिन ग्रुएनबाम

4

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

सबसे अच्छा एपीआई बनाने के लिए, इसका उत्तर यह है कि आपको दोनों को लागू करना चाहिए। यह थोड़ा अधिक टाइपिंग है, लेकिन आपके एपीआई के उपयोगकर्ताओं के लिए लंबे समय में बहुत सारे काम बचाएगा।

pullRabbitबस एक मध्यस्थ विधि होनी चाहिए जो प्रकारों की जांच करती है और उस ऑब्जेक्ट प्रकार (जैसे pullRabbitOutOfHtmlElement) से जुड़े उचित फ़ंक्शन को कॉल करती है ।

इस तरह, जबकि प्रोटोटाइप उपयोगकर्ता उपयोग कर सकते हैं pullRabbit, लेकिन अगर वे मंदी का नोटिस करते हैं , तो वे अपने अंत पर चेक प्रकार को लागू कर सकते हैं (संभवत: तेज तरीके से) और बस pullRabbitOutOfHtmlElementसीधे कॉल करें ।


2

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

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

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

String.prototype.pullRabbit = function(){
    //do something string-relevant
}

HTMLElement.prototype.pullRabbit = function(){
    //do something HTMLElement-relevant
}

Magician.pullRabbitFrom = function(someThingy){
    return someThingy.pullRabbit();
}

नोट: इसे व्यापक रूप से ऑब्जेक्ट के लिए ऐसा करने के लिए बुरा रूप माना जाता है क्योंकि ऑब्जेक्ट से सब कुछ विरासत में मिला है। मैं व्यक्तिगत रूप से भी समारोह से बचना होगा। कुछ लोग किसी भी देशी कंस्ट्रक्टर के प्रोटोटाइप को छूने के बारे में चींटियों को महसूस कर सकते हैं जो एक खराब नीति नहीं हो सकती है, लेकिन उदाहरण तब भी काम कर सकता है जब आपके अपने ऑब्जेक्ट कंस्ट्रक्टर्स के साथ काम कर रहे हों।

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

सौभाग्य से, आप हमेशा केवल पूर्व-प्रकार के प्रकारों या रचनाकारों के नामों को विधियों से जोड़ सकते हैं (IE IE = 8 जिसके पास <ऑब्जेक्ट> .constructor.name नहीं है, आपको इसकी आवश्यकता है कि आप इसे कंस्ट्रक्टर संपत्ति से प्राप्त परिणामों से हटा दें)। आप अभी भी निर्माणकर्ता के नाम की जाँच कर रहे हैं (टाइपो वस्तुओं की तुलना करते समय जेएस में बेकार है) लेकिन कम से कम यह एक विशाल स्विच स्टेटमेंट की तुलना में बहुत अच्छा है या यदि विधि की हर कॉल में एक और श्रृंखला है जो एक विस्तृत हो सकती है। वस्तुओं की विविधता।

var rabbitPullMap = {
    String: ( function pullRabbitFromString(){
        //do stuff here
    } ),
    //parens so we can assign named functions if we want for helpful debug
    //yes, I've been inconsistent. It's just a nice unrelated trick
    //when you want a named inline function assignment

    HTMLElement: ( function pullRabitFromHTMLElement(){
        //do stuff here
    } )
}

Magician.pullRabbitFrom = function(someThingy){
    return rabbitPullMap[someThingy.constructor.name]();
}

या एक ही मानचित्र दृष्टिकोण का उपयोग करते हुए, यदि आप उन्हें उपयोग करने के लिए विभिन्न ऑब्जेक्ट प्रकारों के 'इस' घटक तक पहुंच चाहते थे जैसे कि वे अपने विरासत वाले प्रोटोटाइप को छूने के बिना तरीके थे:

var rabbitPullMap = {
    String: ( function(obj){

    //yes the anon wrapping funcs would make more sense in one spot elsewhere.

        return ( function pullRabbitFromString(obj){
            var rabbitReach = this.match(/rabbit/g);
            return rabbitReach.length;
        } ).call(obj);
    } ),

    HTMLElement: ( function(obj){
        return ( function pullRabitFromHTMLElement(obj){
            return this.querySelectorAll('.rabbit').length;
        } ).call(obj);
    } )
}

Magician.pullRabbitFrom = function(someThingy){

    var
        constructorName = someThingy.constructor.name,
        rabbitCnt = rabbitPullMap[constructorName](someThingy);

    console.log(
        [
            'The magician pulls ' + rabbitCnt,
            rabbitCnt === 1 ? 'rabbit' : 'rabbits',
            'out of her ' + constructorName + '.',
            rabbitCnt === 0 ? 'Boo!' : 'Yay!'
        ].join(' ');
    );
}

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

नोट: यह सब अप्रयुक्त है, क्योंकि मुझे लगता है कि वास्तव में इसके लिए कोई आरएल उपयोग नहीं है। मुझे यकीन है कि वहाँ टाइपोस / कीड़े हैं।


1

यह (मेरे लिए) जवाब देने के लिए एक दिलचस्प और जटिल सवाल है। मुझे वास्तव में यह सवाल पसंद है इसलिए मैं जवाब देने की पूरी कोशिश करूंगा। यदि आप जावास्क्रिप्ट प्रोग्रामिंग के लिए सभी मानकों पर कोई शोध करते हैं, तो आपको इसे करने के कई "सही" तरीके मिल जाएंगे, क्योंकि लोग इसे करने के "सही" तरीके को टाल रहे हैं।

लेकिन चूंकि आप एक राय की तलाश कर रहे हैं कि कौन सा तरीका बेहतर है। कुछ भी नहीं हुआ।

मैं व्यक्तिगत रूप से "एडहॉक" डिजाइन दृष्टिकोण पसंद करूंगा। एक c ++ / C # बैकग्राउंड से आना, यह मेरे विकास की शैली है। आप एक पुलट्रैबिट अनुरोध बना सकते हैं और यह अनुरोध कर सकते हैं कि एक अनुरोध प्रकार पास किए गए तर्क की जाँच करें और कुछ करें। इसका मतलब है कि आपको इस बात की चिंता करने की ज़रूरत नहीं है कि किसी एक समय में किस प्रकार का तर्क पारित किया जा रहा है। यदि आप सख्त दृष्टिकोण का उपयोग करते हैं, तो आपको अभी भी जांचना होगा कि किस प्रकार का चर है, लेकिन इसके बजाय आप विधि कॉल करने से पहले ऐसा करेंगे। तो अंत में सवाल यह है कि क्या आप कॉल करने से पहले या बाद में टाइप करना चाहते हैं।

मुझे उम्मीद है कि यह मदद करता है, कृपया इस उत्तर के संबंध में अधिक प्रश्न पूछने के लिए स्वतंत्र महसूस करें, मैं अपनी स्थिति स्पष्ट करने की पूरी कोशिश करूंगा।


0

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

  • तार?
  • सरणी?
  • मैप्स?
  • धाराओं?
  • कार्य?
  • डेटाबेस?

समझदारी में समय लगता है। मुझे यकीन भी नहीं है कि यह लिखने के लिए तेज़ है:

Magician.pullRabbit = function(anything) {
  if (anything === undefined) {
    return new Rabbit(); // out of thin air
  } else if (isString(anything)) {
    // more
  } else if (isNumber(anything)) {
    // more
  } else {
      throw new Exception("You can't pull a rabbit out of that!");
  }
  // etc.
};

बनाम:

Magician.pullRabbitFromAir = fromAir() {
    return new Rabbit(); // out of thin air
}
Magician.pullRabbitFromStr = fromString(str)) {
    // more
}
Magician.pullRabbitFromInt = fromInt(int)) {
    // more
};

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


बस आपको बता दें कि जावास्क्रिप्ट आपको ऐसा करने की अनुमति देता है :)
बेंजामिन ग्रुएनबाम

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