क्या किसी वस्तु की क्षमताओं को विशेष रूप से लागू किए गए इंटरफेस द्वारा पहचाना जाना चाहिए?


36

सी # के isऑपरेटर और जावा के instanceofऑपरेटर आपको इंटरफ़ेस पर शाखा करने की अनुमति देते हैं (या, अधिक अकारण, इसके आधार वर्ग) एक वस्तु उदाहरण लागू किया गया है।

क्या इंटरफ़ेस द्वारा प्रदान की जाने वाली क्षमताओं के आधार पर उच्च स्तर की शाखाओं के लिए इस सुविधा का उपयोग करना उचित है?

या एक बेस क्लास को बूलियन वैरिएबल प्रदान करना चाहिए जो एक ऑब्जेक्ट में मौजूद क्षमताओं का वर्णन करने के लिए इंटरफ़ेस प्रदान करता है?

उदाहरण:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

बनाम

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

क्षमता की पहचान के लिए इंटरफ़ेस कार्यान्वयन का उपयोग करने के लिए उनके कोई नुकसान हैं?


यह उदाहरण सिर्फ इतना था, एक उदाहरण। मैं एक अधिक सामान्य अनुप्रयोग के बारे में सोच रहा हूं।


10
अपने दो कोड उदाहरण देखें। कौन सा अधिक पठनीय है?
रॉबर्ट हार्वे

39
बस मेरी राय है, लेकिन मैं पहले एक के साथ सिर्फ इसलिए जाऊंगा क्योंकि यह सुनिश्चित करता है कि आप सुरक्षित प्रकार से डाल सकते हैं। दूसरा यह मानता है कि CanResetPasswordकुछ लागू होने पर ही सही होगा IResetsPassword। अब आप एक संपत्ति बना रहे हैं जो यह निर्धारित करती है कि किस प्रकार की प्रणाली को नियंत्रित करना चाहिए (और अंततः जब आप कलाकारों को पहले से तैयार करेंगे)।
१५:


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

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

जवाबों:


7

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

  1. आप प्रकार प्रणाली को आपके लिए काम करने दे रहे हैं। पहले उदाहरण में, अगर isआग लगी तो डाउनकास्ट निश्चित रूप से काम करेगा। दूसरे रूप में, आपको मैन्युअल रूप से यह सुनिश्चित करने की आवश्यकता है कि केवल ऐसी वस्तुएँ जो IResetsPasswordउस संपत्ति के लिए सही रिटर्न को लागू करती हैं
  2. नाजुक आधार वर्ग। आपको प्रत्येक इंटरफ़ेस के लिए बेस क्लास / इंटरफ़ेस में एक संपत्ति जोड़ने की आवश्यकता है जिसे आप जोड़ना चाहते हैं। यह बोझिल और त्रुटि-प्रवण है।

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

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


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

आप इस तरह की जाँच को आसान बना सकते हैं: (account as IResettable)?.ResetPassword();याvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
बेरिन लोरिट्स 18

89

क्या किसी वस्तु की क्षमताओं को विशेष रूप से लागू किए गए इंटरफेस द्वारा पहचाना जाना चाहिए?

वस्तुओं की क्षमताओं की पहचान बिल्कुल नहीं की जानी चाहिए।

ऑब्जेक्ट का उपयोग करने वाले क्लाइंट को यह जानने के लिए आवश्यक नहीं होना चाहिए कि यह कैसे काम करता है। क्लाइंट को केवल उन चीजों को जानना चाहिए जो वह करने के लिए ऑब्जेक्ट बता सकता है। ऑब्जेक्ट क्या करता है, एक बार यह बताए जाने के बाद, ग्राहकों की समस्या नहीं है।

इसलिए इसके बजाय

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

या

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

विचार करें

account.ResetPassword();

या

account.ResetPassword(authority);

क्योंकि accountपहले से ही पता है कि यह काम करेगा। क्यों पूछते हो? बस इसे बताएं कि आप क्या चाहते हैं और इसे करने दें जो भी यह करने जा रहा है।

इस तरह से कल्पना कीजिए कि क्लाइंट ने काम किया है या नहीं, इस बात की परवाह नहीं है क्योंकि यह कुछ समस्या है। क्लाइंट्स का काम केवल प्रयास करना था। अब ऐसा हो गया है और इससे निपटने के लिए अन्य चीजें मिल गई हैं। इस शैली के कई नाम हैं लेकिन मुझे जो नाम सबसे ज्यादा पसंद है वह है , मत पूछो

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


54
बहुरूपता केवल तभी काम करता है जब मैं नहीं जानता या परवाह नहीं करता कि मैं क्या बात कर रहा हूं। तो जिस तरह से मैं उस स्थिति से निपटता हूं वह सावधानी से इससे बचना है।
कैंडिड_ओरेंज

7
यदि क्लाइंट को वास्तव में यह जानने की आवश्यकता है कि क्या प्रयास सफल हुआ, तो विधि एक बूलियन वापस कर सकती है। if (!account.AttemptPasswordReset()) /* attempt failed */;इस तरह से ग्राहक परिणाम जानता है, लेकिन प्रश्न में निर्णय तर्क के साथ कुछ मुद्दों से बचता है। यदि प्रयास अतुल्यकालिक है, तो आप कॉलबैक पास करेंगे।
रेडियोडफ़

5
@ डेविड हम दोनों अंग्रेजी बोलते हैं। तो मैं आपको इस टिप्पणी को दो बार बढ़ाने के लिए कह सकता हूं। क्या इसका मतलब है कि आप इसे करने में सक्षम हैं? क्या मुझे यह जानने की आवश्यकता है कि क्या आप इसे करने के लिए कह सकते हैं?
कैंडिड_ऑरेंज

5
@QPaysTaxes आप सभी जानते हैं कि एक त्रुटि हैंडलर accountका निर्माण कब किया गया था। इस बिंदु पर पॉपअप, लॉगिंग, प्रिंटआउट और ऑडियो अलर्ट हो सकते हैं। क्लाइंट का काम यह कहना है कि "अभी करो"। इससे अधिक करने की जरूरत नहीं है। आपको एक जगह पर सब कुछ करने की ज़रूरत नहीं है।
candied_orange

6
@QPaysTaxes यह कहीं और और विभिन्न तरीकों से संभाला जा सकता है, यह इस बातचीत के लिए बहुत ही अच्छा है और केवल पानी को मैला कर देगा।
टॉम। बोवेन89

17

पृष्ठभूमि

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

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

यह एक महत्वपूर्ण कौशल है कि हम में से सबसे अनुभवी भी गलत हो जाते हैं: आवश्यकताओं को ठीक से पहचानना और उन्हें मशीन भाषाओं में अनुवाद करना।

आपका प्रश्न

आपने जो लिखा है, उसके आधार पर, मुझे लगता है कि आप वैकल्पिक क्रियाओं के सामान्य मामले को गलत समझ सकते हैं जो कक्षाएं कर सकती हैं। हां, मुझे पता है कि आपका कोड सिर्फ एक उदाहरण है और आप सामान्य मामले में रुचि रखते हैं। हालाँकि, ऐसा लगता है कि आप जानना चाहते हैं कि ऐसी स्थिति को कैसे संभालना है जहाँ किसी वस्तु के कुछ उपप्रकार एक क्रिया कर सकते हैं, लेकिन अन्य उपप्रकार नहीं कर सकते हैं।

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

मेरा समाधान

एक खाता वर्ग पर विचार करें जिसमें कई वैकल्पिक क्रियाएं हो सकती हैं। उत्तराधिकार के माध्यम से "एक्शन एक्स प्रदर्शन कर सकते हैं" को परिभाषित करने के बजाय, एक प्रतिनिधि ऑब्जेक्ट (पासवर्ड रीसेट्टर, फॉर्म सबमिटर, आदि) या एक एक्सेस टोकन क्यों नहीं लौटाया जाना चाहिए?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

अंतिम विकल्प का लाभ यह है कि क्या होगा यदि मैं किसी और के पासवर्ड को रीसेट करने के लिए अपने खाता क्रेडेंशियल्स का उपयोग करना चाहता हूं ?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

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


TL; DR: यह एक XY समस्या की तरह पढ़ता है । आम तौर पर जब दो विकल्पों के साथ सामना किया जाता है जो कि सबॉप्टीमल होते हैं, तो यह एक कदम वापस लेने और सोचने के लायक है "मैं वास्तव में यहां क्या हासिल करने की कोशिश कर रहा हूं? क्या मुझे वास्तव में यह सोचना चाहिए कि टाइपकास्टिंग कम बदसूरत कैसे बनाया जाए, या क्या मुझे तरीकों की तलाश करनी चाहिए?" पूरी तरह से टाइपकास्ट हटा दें? "


1
यदि आप वाक्यांश का उपयोग नहीं करते हैं, तो भी डिजाइन की बदबू आती है।
जारेड स्मिथ

उन मामलों के बारे में जो रीसेट पासवर्ड प्रकार के आधार पर पूरी तरह से अलग तर्क हैं? ResetPassword (pswd) बनाम ResetPasswordToRandom ()
TheCatWhisperer

1
@ TheCatWhisperer पहला उदाहरण "पासवर्ड बदलें" है जो एक अलग कार्रवाई है। दूसरा उदाहरण है कि मैं इस उत्तर में क्या वर्णन करता हूं।

क्यों नहीं account.getPasswordResetter().resetPassword();?
AnoE

1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. आसान। आप दूसरे खाते के लिए एक खाता डोमेन ऑब्जेक्ट बनाते हैं और उस एक का उपयोग करते हैं। इकाई परीक्षण के लिए स्टेटिक कक्षाएं समस्याग्रस्त हैं (यह विधि परिभाषा के अनुसार कुछ भंडारण तक पहुंच की आवश्यकता होती है, इसलिए अब आप आसानी से किसी भी कोड को इकाई परीक्षण नहीं कर सकते जो उस वर्ग को छूता है) और सबसे अच्छा बचा। कुछ अन्य इंटरफ़ेस को वापस करने का विकल्प अक्सर एक अच्छा विचार होता है लेकिन वास्तव में अंतर्निहित समस्या से नहीं निपटता है (आप समस्या को अन्य इंटरफ़ेस में स्थानांतरित कर देते हैं)।
वू

1

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

हालांकि, यदि आप एक और दृष्टिकोण का उपयोग करना चाहते हैं, तो याक को शेव करने के कई तरीके हैं।

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

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

यह एक बूलियन को यह संकेत दे सकता है कि क्या रीसेट सफल था। उस बूलियन पर स्विच करते हुए, हम संबंधित कर सकते हैं कि पासवर्ड रीसेट विफल हो गया।

यह पासवर्ड रीसेट प्रयास के परिणाम को इंगित करते हुए एक स्ट्रिंग वापस कर सकता है। स्ट्रिंग अधिक विवरण दे सकता है कि क्यों एक रीसेट विफल रहा और आउटपुट हो सकता है।

यह एक ResetResult ऑब्जेक्ट को लौटा सकता है जो अधिक विवरण बताता है और सभी पिछले रिटर्न तत्वों को जोड़ता है।

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

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

वंशानुक्रम से अधिक विकल्प एक विकल्प हो सकता है: आपके पास अंतिम passwordResetterफ़ील्ड (या समतुल्य गेट्टर) और समतुल्य वर्ग (एस) हो सकते हैं जिन्हें आप कॉल करने से पहले शून्य के लिए जांच सकते हैं। हालांकि यह अनुमति मांगने जैसा थोड़ा काम करता है, अंतिम रूप से किसी भी अनुमानित गतिशील प्रकृति से बचा जा सकता है।

आप इसकी अपनी कक्षा में कार्यक्षमता को बढ़ाने के बारे में सोच सकते हैं जो एक पैरामीटर के रूप में एक खाता ले सकता है और उस पर कार्य कर सकता है (उदाहरण के लिए resetter.reset(account)), हालांकि यह अक्सर बुरा व्यवहार भी होता है (एक आम सादृश्य दुकानदार नकदी प्राप्त करने के लिए आपके बटुए में पहुंचता है)।

क्षमता वाली भाषाओं में, आप मिश्रण या लक्षण का उपयोग कर सकते हैं, हालाँकि, आप उस स्थिति में समाप्त हो जाते हैं जहाँ आपने शुरू किया था जहाँ आप उन क्षमताओं के अस्तित्व के लिए जाँच कर रहे होंगे।


सभी खातों में पासवर्ड नहीं हो सकता (वैसे भी इस विशेष प्रणाली के परिप्रेक्ष्य से)। उदाहरण के लिए खाता मेरी तीसरी पार्टी हो ... Google, facebook, ect। यह नहीं कहने के लिए कि यह एक महान जवाब नहीं है!
theCatWhisperer

0

" एक पासवर्ड रीसेट कर सकते हैं " के इस विशिष्ट उदाहरण के लिए , मैं अनुशंसा करूँगा कि मैं इनहेरिटेंस (इस मामले में, इंटरफ़ेस / अनुबंध की विरासत) पर रचना का उपयोग करूं । क्योंकि, ऐसा करने से:

class Foo : IResetsPassword {
    //...
}

आप तुरंत (संकलन समय पर) निर्दिष्ट कर रहे हैं कि आपकी कक्षा ' एक पासवर्ड रीसेट कर सकती है '। लेकिन, यदि आपके परिदृश्य में, क्षमता की उपस्थिति सशर्त है और अन्य चीजों पर निर्भर करती है, तो आप संकलन समय पर चीजों को निर्दिष्ट नहीं कर सकते। फिर, मैं यह करने का सुझाव देता हूं:

class Foo {

    PasswordResetter passwordResetter;

}

अब, रनटाइम पर, आप myFoo.passwordResetter != nullयह ऑपरेशन करने से पहले देख सकते हैं । यदि आप सामान को और अधिक कम करना चाहते हैं (और आप कई और क्षमताओं को जोड़ने की योजना बना रहे हैं), तो आप कर सकते हैं:

class Foo {
    //... foo stuff
}

class PasswordResetOperation {
    bool Execute(Foo foo) { ... }
}

class SendMailOperation {
    bool Execute(Foo foo) { ... }
}

//...and you follow this pattern for each new capability...

अद्यतन करें

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

class BaseAccount {
    //...
}
class GuestAccount : BaseAccount {
    //...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
    //...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
    //...
}

//Capabilities

interface IMyPasswordReset {
    bool ResetPassword();
}

interface IPasswordReset {
    bool ResetPassword(UserAccount userAcc);
}

interface IEditPosts {
    bool EditPost(long postId, ...);
}

interface ISendMail {
    bool SendMail(string from, string to, ...);
}

अब, मैं उल्लिखित सभी विकल्पों का विश्लेषण करने की कोशिश करूँगा:

ओपी दूसरा उदाहरण:

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

मान लीजिए कि यह कोड कुछ आधार खाता वर्ग प्राप्त कर रहा है (जैसे: BaseAccountमेरे उदाहरण में); यह बुरा है क्योंकि यह बेस क्लास में बूलियन्स डाल रहा है, इसे कोड के साथ प्रदूषित कर रहा है जिससे कोई मतलब नहीं है।

ओपी पहला उदाहरण:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

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

CandiedOrange का ऐक्सरसाइज:

account.ResetPassword(authority);

यदि इस ResetPasswordविधि को BaseAccountकक्षा में डाला जाता है , तो यह ओप के दूसरे उदाहरण की तरह, अनुचित कोड के साथ आधार वर्ग को भी प्रदूषित कर रहा है

स्नोमैन का जवाब:

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

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

मेरी ओर से एक और सुझाव:

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

//Template method
class BaseAccountOperation {

    BaseAccount account;

    void Execute() {

        //... some processing

        TryResetPassword();

        //... some processing

        TrySendMail();

        //... some processing
    }

    void TryResetPassword() {
        Print("Not allowed to reset password with this account type!");
    }

    void TrySendMail() {
        Print("Not allowed to reset password with this account type!");
    }
}

class UserAccountOperation : BaseAccountOperation {

    UserAccount userAccount;

    void TryResetPassword() {
        account.ResetPassword(...);
    }

}

class AdminAccountOperation : BaseAccountOperation {

    AdminAccount adminAccount;

    override void TryResetPassword() {
        account.ResetPassword(...);
    }

    void TrySendMail() {
        account.SendMail(...);
    }
}

आप डिक्शनरी / हैशटेबल का उपयोग करके ऑपरेशन को उपयुक्त खाता वर्ग में बाँध सकते हैं, या विस्तार विधियों का उपयोग करके रन-टाइम संचालन कर dynamicसकते हैं, कीवर्ड का उपयोग कर सकते हैं, या अंतिम विकल्प के रूप में ऑपरेशन के लिए खाता ऑब्जेक्ट पास करने के लिए केवल एक कास्ट का उपयोग करें (में इस मामले में जातियों की संख्या केवल एक ही है, ऑपरेशन की शुरुआत में)।


1
क्या यह हमेशा "Execute ()" पद्धति को लागू करने के लिए काम करेगा लेकिन कभी-कभी इसमें कुछ भी नहीं होता है? यह एक बूल लौटाता है, इसलिए मैं अनुमान लगा रहा हूं कि इसका मतलब यह था या नहीं। यह ग्राहक को वापस फेंकता है: "मैंने पासवर्ड रीसेट करने की कोशिश की, लेकिन ऐसा नहीं हुआ (जो भी कारण हो), इसलिए अब मुझे करना चाहिए ..."

4
"CanX ()" और "doX ()" विधियाँ एक एंटीपैर्टन है: यदि कोई इंटरफ़ेस "doX ()" विधि को उजागर करता है, तो यह X करने में सक्षम हो सकता है, भले ही X का मतलब no-op (उदा null ऑब्जेक्ट पैटर्न) हो )। यह कभी भी अस्थायी युग्मन में संलग्न करने के लिए एक इंटरफ़ेस के उपयोगकर्ताओं पर अवलंबी नहीं होना चाहिए (विधि बी से पहले विधि ए लागू करें) क्योंकि यह गलत होने और चीजों को तोड़ने के लिए बहुत आसान है।

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

दिलचस्प: शीर्ष-मतदान जवाब मूल रूप से कहता है कि ऊपर मेरी टिप्पणी क्या कहती है। कुछ दिन तुम बस भाग्यशाली हो।

@nocomprende - हाँ, मैंने कई टिप्पणियों के अनुसार अपना उत्तर अपडेट किया। प्रतिक्रिया के लिए धन्यवाद :)
एमर्सन कार्डसो

0

आपके द्वारा प्रस्तुत किए गए विकल्पों में से कोई भी अच्छा OO नहीं है। यदि आप लिख रहे हैं कि किसी वस्तु के प्रकार के आस-पास के कथन, तो आप सबसे अधिक OO गलत कर रहे हैं (अपवाद हैं, यह एक नहीं है।) यहाँ आपके प्रश्न का सरल OO उत्तर है (मान्य नहीं हो सकता है C #):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

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

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


सभी खातों को रीसेट नहीं किया जा सकता है। इसके अलावा, क्या किसी खाते में रीसेट होने की तुलना में अधिक कार्यक्षमता हो सकती है?
द कैटरवर्जर

2
"सभी खातों को रीसेट नहीं किया जा सकता है।" यकीन नहीं है कि अगर यह संपादित करने से पहले लिखा गया था। दूसरा वर्ग है NotResetable। "एक खाते में रीसेट होने की तुलना में अधिक कार्यक्षमता हो सकती है" मैं ऐसा उम्मीद करूंगा लेकिन यह सवाल पर महत्वपूर्ण नहीं लगता है।
जिमीजम्स

1
यह समाधान सही दिशा में जाता है, मुझे लगता है, क्योंकि यह ओओ के अंदर एक अच्छा समाधान प्रदान करता है। संभवतः इंटरफ़ेस को नाम बदलने के लिए IPasswordReseter (या उस प्रभाव के लिए कुछ), इंटरफेस को अलग करना होगा। IAccount को कई अन्य इंटरफेस के समर्थन के रूप में घोषित किया जा सकता है। इसके बाद आपको कार्यान्वयन के साथ कक्षाएं लेने का विकल्प मिलेगा जो तब डोमेन ऑब्जेक्ट्स पर डेलिगेशन द्वारा बनाए गए और उपयोग किए जाते हैं। @JimmyJames, C # दोनों वर्गों के मामूली न्यूपिक को यह निर्दिष्ट करने की आवश्यकता होगी कि वे IAccount लागू करते हैं। अन्यथा कोड थोड़ा अजीब लगता है ;-)
मियामोतो अकीरा

1
इसके अलावा, CanX () और DoX () के बारे में अन्य उत्तरों में से एक में स्नोमैन की एक टिप्पणी की जाँच करें। हमेशा एक विरोधी पैटर्न नहीं, हालांकि, जैसा कि इसका उपयोग होता है (उदाहरण के लिए यूआई व्यवहार पर)
मियामोतो अकीरा

1
यह उत्तर अच्छी तरह से शुरू होता है: यह खराब ओओपी है। दुर्भाग्य से आपका समाधान सिर्फ उतना ही बुरा है क्योंकि यह प्रकार प्रणाली को भी प्रभावित करता है लेकिन एक अपवाद को फेंकता है। एक अच्छा समाधान अलग दिखता है: एक प्रकार पर अनिमित तरीके नहीं हैं। .NET फ्रेमवर्क वास्तव में कुछ प्रकारों के लिए आपके दृष्टिकोण का उपयोग करता है लेकिन यह स्पष्ट रूप से एक गलती थी।
कोनराड रुडोल्फ

0

उत्तर

आपके प्रश्न का मेरा थोड़ा राय वाला जवाब है:

किसी ऑब्जेक्ट की क्षमताओं को स्वयं के माध्यम से पहचाना जाना चाहिए, न कि किसी इंटरफ़ेस को लागू करने से। एक सांख्यिकीय, दृढ़ता से टाइप की गई भाषा इस संभावना को सीमित करेगी, हालांकि (डिजाइन द्वारा)।

परिशिष्ट:

ऑब्जेक्ट प्रकार पर शाखा करना बुराई है।

पर्यटन का

मैं देखता हूं कि आपने c#टैग नहीं जोड़ा है , लेकिन सामान्य object-orientedसंदर्भ में पूछ रहे हैं ।

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

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

तो, ऐसी भाषा में (रूबी, उदाहरण के लिए), आपका कोड बन जाता है:

account.reset_password

अवधि।

accountया तो पासवर्ड रीसेट हो जाएगा, एक "अस्वीकृत" अपवाद फेंक, या एक "कैसे 'reset_password' को संभालने के लिए पता नहीं है" अपवाद फेंक देते हैं।

यदि आपको किसी भी कारण से स्पष्ट रूप से शाखा करने की आवश्यकता है, तो आप अभी भी क्षमता पर ऐसा करेंगे, वर्ग पर नहीं:

account.reset_password  if account.can? :reset_password

(जहां can?सिर्फ एक विधि है जो यह जांचती है कि वस्तु किसी विधि को निष्पादित कर सकती है, बिना कॉल किए।

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


आपका दृष्टिकोण यहाँ अच्छा है! हम जावा / C # तरफ OOP की एक अलग शाखा को भूल जाते हैं। जब मैं अपने सहयोगियों को बताता हूं, जेएस (इसकी सभी समस्याओं के लिए), कई मायनों में, सी # की तुलना में अधिक ओओ, वे मुझ पर हंसते हैं!
TheCatWhisperer

मुझे C # पसंद है, और मुझे लगता है कि यह एक अच्छी सामान्य प्रयोजन की भाषा है, लेकिन यह मेरी निजी राय है, कि OO के संदर्भ में, यह काफी खराब है। दोनों सुविधाओं और अभ्यास में, यह प्रक्रियात्मक प्रोग्रामिंग को प्रोत्साहित करने के लिए जाता है।
TheCatWhisperer

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

0

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

आपका दूसरा विकल्प OOP के नजरिए से ठीक हो सकता है, लेकिन टाइप कास्टिंग मुझे भ्रमित करती है। यदि आपका खाता आपको अपना पासवर्ड रीसेट करने देता है, तो आपको टाइपकास्टिंग की आवश्यकता क्यों है? इसलिए यह मानते हुए कि आपका CanResetPasswordखंड कुछ मूलभूत व्यावसायिक आवश्यकता का प्रतिनिधित्व करता है जो कि खाते के अमूर्त होने का एक हिस्सा है, आपका दूसरा विकल्प ठीक है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.