यदि हम SetWidth और SetHeight तरीकों को ओवरराइड करते हैं, तो आयत से वर्ग वर्गाकार समस्याग्रस्त क्यों होगी?


105

यदि एक वर्ग आयत का एक प्रकार है, तो एक वर्ग एक आयत से विरासत में क्यों नहीं मिल सकता है? या यह एक बुरा डिजाइन क्यों है?

मैंने लोगों को कहते सुना है:

यदि आपने रेक्टेंगल से स्क्वायर व्युत्पन्न किया है, तो एक स्क्वायर कहीं भी प्रयोग करने योग्य होना चाहिए, जहां आप आयत की अपेक्षा करते हैं

यहां क्या समस्या है? और वर्गाकार आयत की अपेक्षा में स्क्वायर क्यों प्रयोग करने योग्य होगा? यदि हम स्क्वायर ऑब्जेक्ट बनाते हैं, तो यह केवल प्रयोग करने योग्य होगा, और यदि हम स्क्वायर के लिए सेटविदथ और सेटहाइट तरीकों को ओवरराइड करते हैं तो कोई समस्या क्यों होगी?

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

क्या कोई उपरोक्त तर्कों की व्याख्या कर सकता है? फिर, अगर हम स्क्वायर में सेटविड और सेटहाइट तरीकों की सवारी करते हैं, तो क्या यह इस समस्या को हल नहीं करेगा?

मैंने भी सुना / पढ़ा है:

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

यहां मेरा मानना ​​है कि "पुन: प्रयोज्य" सही शब्द है। आयताकार "पुन: प्रयोज्य" हैं और इसलिए वर्ग हैं। क्या मुझे उपरोक्त तर्क में कुछ याद आ रहा है? एक वर्ग को किसी भी आयत की तरह फिर से आकार दिया जा सकता है।


15
यह सवाल काफी सारगर्भित लगता है। कक्षाओं और वंशानुक्रम का उपयोग करने के लिए एक gazillion तरीके हैं, चाहे कुछ वर्ग से कुछ वर्ग को विरासत में मिला या नहीं, यह एक अच्छा विचार है जो आमतौर पर ज्यादातर उन वर्गों का उपयोग कैसे करना चाहते हैं पर निर्भर करता है। एक व्यावहारिक मामले के बिना मैं यह नहीं देख सकता कि इस प्रश्न का प्रासंगिक उत्तर कैसे मिल सकता है।
आआआआआआ आआआआआ

2
कुछ सामान्य ज्ञान यह याद किया जाता है कि वर्ग का उपयोग करना है , एक आयत इसलिए यदि आपके वर्ग वर्ग की वस्तु जहां आयत यह आवश्यक है नहीं किया जा सकता शायद कुछ आवेदन डिजाइन दोष वैसे भी है।
शतुलु

7
मुझे लगता है कि बेहतर सवाल है Why do we even need Square? दो पेन होने जैसा है। एक नीली कलम और एक लाल नीली, पीली या हरी कलम। नीली कलम बेमानी है - वर्ग के मामले में और भी अधिक क्योंकि इसमें कोई लागत लाभ नहीं है।
गुस्डोर

2
@eBusiness इसकी अमूर्तता यह है कि यह एक अच्छा सीखने का सवाल है। यह समझना महत्वपूर्ण है कि विशेष उपयोग के मामलों में स्वतंत्र रूप से घटाव के कौन से उपयोग खराब हैं।
डोभाल

5
@ कथुलु वास्तव में नहीं। सबटाइपिंग व्यवहार के बारे में है और एक परिवर्तनशील वर्ग एक परस्पर आयत की तरह व्यवहार नहीं करता है। यही कारण है कि "एक है ..." रूपक बुरा है।
डोभाल

जवाबों:


189

मूल रूप से हम चीजों को समझदारी से व्यवहार करना चाहते हैं।

निम्नलिखित समस्या पर विचार करें:

मुझे आयतों का एक समूह दिया गया है और मैं उनका क्षेत्र 10% बढ़ाना चाहता हूँ। इसलिए मैं जो कुछ करता हूं, वह आयत की लंबाई 1.1 गुना करने के लिए सेट किया गया है जो पहले था।

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    rectangle.Length = rectangle.Length * 1.1;
  }
}

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

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

इससे भी बुरी बात यह है कि लेखांकन से जिम मेरे तरीके को देखता है और कुछ अन्य कोड लिखता है जो इस तथ्य का उपयोग करता है कि यदि वह मेरी विधि में वर्गों को पारित करता है, तो उसे आकार में बहुत अच्छा 21% वृद्धि मिलती है। जिम खुश है और कोई भी समझदार नहीं है।

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

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

अल्फ्रेड अपने uber हैकिंग कौशल के साथ खुश है और जिल बंद बग तय है कि संकेत।

अगले महीने किसी को भुगतान नहीं किया जाता है क्योंकि लेखांकन IncreaseRectangleSizeByTenPercentविधि में वर्गों को पारित करने में सक्षम होने और 21% के क्षेत्र में वृद्धि प्राप्त करने पर निर्भर था । समस्या के स्रोत को ट्रैक करने के लिए पूरी कंपनी "प्राथमिकता 1 बगफिक्स" मोड में जाती है। वे अल्फ्रेड को ठीक करने के लिए समस्या का पता लगाते हैं। वे जानते हैं कि उन्हें लेखांकन और विज्ञापन दोनों को खुश रखना होगा। तो वे विधि कॉल के साथ उपयोगकर्ता की पहचान करके समस्या को ठीक करते हैं:

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
  IncreaseRectangleSizeByTenPercent(
    rectangles, 
    new User() { Department = Department.Accounting });
}

public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
  foreach(var rectangle in rectangles)
  {
    if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
    {
      rectangle.Length = rectangle.Length * 1.1;
    }
    else if (typeof(rectangle) == Square)
    {
      rectangle.Length = rectangle.Length * 1.04880884817;
    }
  }
}

इत्यादि इत्यादि।

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

इस समस्या को ठीक करने के दो यथार्थवादी तरीके हैं।

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

दूसरा तरीका वर्गों और आयतों के बीच विरासत श्रृंखला को तोड़ने का है। एक वर्ग के लिए एक एकल के रूप में परिभाषित किया जाता है तो SideLengthसंपत्ति और आयतों एक है Lengthऔर Widthसंपत्ति और वहाँ कोई विरासत है, यह गलती से एक आयत की उम्मीद है और एक वर्ग हो रही द्वारा बातें तोड़ने के लिए असंभव है। C # शब्दों में, आप sealअपना आयत वर्ग बना सकते हैं, जो यह सुनिश्चित करता है कि आपके द्वारा प्राप्त किए गए सभी आयत वास्तव में Rectangles हैं।

इस मामले में, मुझे समस्या को ठीक करने का "अपरिवर्तनीय वस्तुएं" तरीका पसंद है। एक आयत की पहचान इसकी लंबाई और चौड़ाई है। यह समझ में आता है कि जब आप किसी वस्तु की पहचान बदलना चाहते हैं, तो आप वास्तव में जो चाहते हैं, वह एक नई वस्तु है। यदि आप एक पुराने ग्राहक को खो देते हैं और एक नया ग्राहक प्राप्त करते हैं, तो आप Customer.Idपुराने ग्राहक से नए क्षेत्र में परिवर्तन नहीं करते हैं , आप एक नया बनाते हैं Customer

Liskov प्रतिस्थापन सिद्धांत के उल्लंघन वास्तविक दुनिया में आम हैं, ज्यादातर इसलिए क्योंकि वहां बहुत से कोड ऐसे लोगों द्वारा लिखे गए हैं जो अक्षम / समय के दबाव में हैं / गलतियों की परवाह नहीं करते / करते हैं। यह कुछ बहुत ही खराब समस्याओं को जन्म दे सकता है। ज्यादातर मामलों में, आप इसके बजाय विरासत पर रचना का पक्ष लेना चाहते हैं ।


7
लिस्कोव एक बात है, और भंडारण एक और मुद्दा है। अधिकांश कार्यान्वयन में, आयत से विरासत में मिली एक स्क्वायर आवृत्ति के लिए दो आयामों को संग्रहीत करने के लिए स्थान की आवश्यकता होगी, भले ही केवल एक की आवश्यकता हो।
el.pescado

29
बिंदु का वर्णन करने के लिए एक कहानी का शानदार उपयोग
रोरी हंटर

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

33
@ रोयट: एक आयत को यह जानना चाहिए कि उसका क्षेत्र कैसे निर्धारित किया जाए? यह पूरी तरह से लंबाई और चौड़ाई से प्राप्त संपत्ति है। और अधिक बिंदु तक, किस आयाम को बदलना चाहिए - लंबाई, चौड़ाई, या दोनों?
cHao

32
@ राय टी। यह कहना बहुत अच्छा है कि आपने समस्या को अलग तरीके से हल किया होगा, लेकिन तथ्य यह है कि यह एक उदाहरण है - यद्यपि वास्तविक सरलीकरण - वास्तविक दुनिया की ऐसी परिस्थितियाँ जो विरासत उत्पादों को बनाए रखते हुए दैनिक आधार पर डेवलपर्स का सामना करती हैं। और यहां तक ​​कि अगर आपने उस तरीके को लागू किया है, तो एलएसपी का उल्लंघन करने वाले और इस पर बग की शुरुआत करने वाले वंश को रोक नहीं पाएंगे। यही कारण है कि .NET फ्रेमवर्क में हर वर्ग बहुत अधिक सील है।
स्टीफन

30

यदि आपकी सभी वस्तुएँ अपरिवर्तनीय हैं, तो कोई समस्या नहीं है। हर स्क्वायर एक आयत भी है। एक आयत के सभी गुण एक वर्ग के गुण भी हैं।

समस्या तब शुरू होती है जब आप वस्तुओं को संशोधित करने की क्षमता जोड़ते हैं। या वास्तव में - जब आप ऑब्जेक्ट को तर्क देना शुरू करते हैं, न कि केवल संपत्ति पाने वालों को पढ़ना।

ऐसे संशोधन हैं जो आप एक आयत के लिए कर सकते हैं जो आपके आयत वर्ग के सभी आवेषणों को बनाए रखते हैं, लेकिन सभी वर्ग अचरों को नहीं - जैसे कि चौड़ाई या ऊँचाई को बदलना। अचानक एक आयत का व्यवहार सिर्फ इसके गुण नहीं हैं, यह इसके संभावित संशोधन भी हैं। यह सिर्फ वही नहीं है जो आप आयत से बाहर निकलते हैं, यह वही है जो आप डाल सकते हैं

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

जब आप देखते हैं कि आप एक आयत और एक वर्ग में "क्या डाल सकते हैं", तो आप उन्हें क्या संदेश भेज सकते हैं, आपको संभावना है कि कोई भी संदेश जिसे आप वैध रूप से एक वर्ग को भेज सकते हैं, आप एक आयत को भी भेज सकते हैं।

यह सह-विचरण बनाम गर्भ-विचरण की बात है।

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

  • केवल वे मान लौटाएं जो सुपरक्लास वापस आएंगे - अर्थात, वापसी प्रकार सुपरक्लास पद्धति के रिटर्न प्रकार का एक उपप्रकार होना चाहिए। रिटर्न सह-संस्करण है।
  • सभी मानों को स्वीकार करें जो सुपरटाइप स्वीकार करेगा - अर्थात, तर्क प्रकार सुपरक्लास पद्धति के तर्क प्रकारों के सुपरटेप्स होने चाहिए। तर्क गर्भ-निरोधक हैं।

तो, आयत और वर्ग पर वापस: क्या स्क्वायर आयत का एक उपवर्ग हो सकता है, यह पूरी तरह से निर्भर करता है कि आयत के कौन से तरीके हैं।

यदि आयत में चौड़ाई और ऊँचाई के लिए अलग-अलग बसने हैं, तो स्क्वायर एक अच्छा उपवर्ग नहीं बनाएगा।

इसी तरह, यदि आप कुछ तरीकों को तर्कों में सह-संस्करण बनाते हैं, जैसे कि compareTo(Rectangle)आयत और compareTo(Square)वर्ग पर, तो आपको वर्ग को आयत के रूप में उपयोग करने में समस्या होगी।

यदि आप अपने स्क्वायर और आयत को संगत करने के लिए डिज़ाइन करते हैं, तो यह काम करेगा, लेकिन उन्हें एक साथ विकसित किया जाना चाहिए, या मैं शर्त लगाऊंगा कि यह काम नहीं करेगा।


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

11
मुझे यह दिलचस्प लगा, यहां तक ​​कि जब यह "जाहिरा तौर पर अप्रासंगिक" है
जेसविन जोस

14
@gnat मैं तर्क देता हूं कि यह प्रासंगिक है क्योंकि प्रश्न का वास्तविक मूल्य पहचान रहा है जब दो प्रकारों के बीच वैध उप-संबंध स्थापित होता है। यह इस बात पर निर्भर करता है कि सुपरपाइप किस ऑपरेशन की घोषणा करता है, इसलिए यह इंगित करने के लायक है कि यदि समस्या उत्परिवर्ती तरीकों से दूर हो जाती है।
डोभाल

1
@gnat भी setters हैं mutators , तो LRN अनिवार्य रूप से कह रहा है, "ऐसा नहीं है और यह एक समस्या नहीं है।" मैं सरल प्रकारों के लिए अपरिवर्तनीयता से सहमत होने के लिए होता हूं, लेकिन आप एक अच्छा बिंदु बनाते हैं: जटिल वस्तुओं के लिए, समस्या इतनी सरल नहीं है।
पैट्रिक एम

1
इस तरह से विचार करें, 'आयत' वर्ग द्वारा गारंटीकृत व्यवहार क्या है? कि आप एक दूसरे की चौड़ाई और ऊंचाई बदल सकते हैं। (अर्थात सेटविथ और सेटहाइट) विधि। अब यदि स्क्वायर आयत से लिया गया है, तो स्क्वायर को इस व्यवहार की गारंटी देनी होगी। चूँकि वर्ग इस व्यवहार की गारंटी नहीं दे सकता, इसलिए यह एक बुरी विरासत है। हालाँकि, यदि SetWidth / SetHeight Methods को Rectangle class से हटा दिया जाता है, तो ऐसा कोई व्यवहार नहीं है और इसलिए आप Square class को Rectangle से प्राप्त कर सकते हैं।
नितिन भिडे

17

यहाँ बहुत सारे अच्छे उत्तर हैं; विशेष रूप से स्टीफन का जवाब यह बताने का एक अच्छा काम करता है कि प्रतिस्थापन सिद्धांत के उल्लंघन के कारण टीमों के बीच वास्तविक दुनिया का टकराव क्यों होता है।

मुझे लगा कि मैं LSP के अन्य उल्लंघनों के लिए एक रूपक के रूप में उपयोग करने के बजाय आयतों और वर्गों की विशिष्ट समस्या के बारे में संक्षेप में बात कर सकता हूं।

वर्ग-ए-खास-खास-की-आयत के साथ एक अतिरिक्त समस्या है जो शायद ही कभी उल्लेख किया जाता है, और वह है: हम वर्गों और आयतों के साथ क्यों रोक रहे हैं ? यदि हम यह कहना चाहते हैं कि एक वर्ग एक विशेष प्रकार की आयत है, तो निश्चित रूप से हमें यह भी कहना चाहिए:

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

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


3
यदि आकृतियों की वस्तुएं अपरिवर्तनीय हैं, तो एक IShapeप्रकार हो सकता है जिसमें एक बाउंडिंग बॉक्स शामिल है, और खींचा जा सकता है, स्केल किया जा सकता है, और क्रमबद्ध किया जा सकता है, और एक IPolygonउपप्रकार की विधि के साथ संख्याओं की संख्या और एक विधि को वापस करने की रिपोर्ट कर सकता है IEnumerable<Point>। एक तो IQuadrilateralउप-प्रकार हो सकता है जो व्युत्पन्न करता है IPolygon, IRhombusऔर IRectangle, उस से प्राप्त होता है, और से ISquareप्राप्त होता है IRhombusऔर IRectangle। उत्परिवर्तन खिड़की से बाहर सब कुछ फेंक देता है, और कई विरासत कक्षाओं के साथ काम नहीं करता है, लेकिन मुझे लगता है कि यह अपरिवर्तनीय इंटरफेस के साथ ठीक है।
सुपरकैट

मैं एरिक के साथ प्रभावी रूप से यहाँ असहमत हूँ (हालांकि एक -1 के लिए पर्याप्त नहीं है!)। वे सभी रिश्ते (संभवतः) प्रासंगिक हैं, जैसा कि @supercat उल्लेख है; यह सिर्फ एक YAGNI मुद्दा है: आप इसे तब तक लागू नहीं करते जब तक आपको इसकी आवश्यकता न हो।
मार्क हर्ड

बहुत अच्छा जवाब! उच्चतर होना चाहिए।
andrew.fox 19

1
@MarkHurd - यह एक YAGNI मुद्दा नहीं है: प्रस्तावित वंशानुक्रम प्रस्तावित पदानुक्रम में वर्णित वर्गीकरण के आकार का है, लेकिन इसे परिभाषित करने वाले रिश्तों की गारंटी देने के लिए नहीं लिखा जा सकता है। कैसे करता है IRhombusकी गारंटी है कि सभी Pointसे लौटे Enumerable<Point>द्वारा परिभाषित IPolygonबराबर लंबाई के किनारों होते हैं? क्योंकि IRhombusअकेले इंटरफ़ेस का कार्यान्वयन इस बात की गारंटी नहीं देता है कि एक ठोस वस्तु एक समभुज है, उत्तराधिकार उत्तर नहीं हो सकता है।
ए। रेंजर

14

गणितीय दृष्टिकोण से, एक वर्ग एक आयत है। यदि एक गणितज्ञ वर्ग को संशोधित करता है, तो यह अब वर्ग अनुबंध का पालन नहीं करता है, यह एक आयत में बदल जाता है।

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

यहाँ प्रमुख कारक परिवर्तनशीलता है । क्या एक बार निर्माण के बाद आकार बदल सकता है?

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

  • अपरिवर्तनीय: यदि आकृतियाँ एक बार निर्मित नहीं हो सकती हैं, तो एक वर्गाकार वस्तु को भी हमेशा आयत अनुबंध को पूरा करना चाहिए। एक वर्ग का आयत के साथ एक संबंध हो सकता है।

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


1
This is one of those cases where the real world is not able to be modeled in a computer 100%। ऐसा क्यों? हमारे पास अभी भी एक वर्ग और एक आयत का कार्यात्मक मॉडल हो सकता है। एकमात्र परिणाम यह है कि हमें उन दो वस्तुओं पर अमूर्त निर्माण के लिए एक सरल निर्माण की तलाश करनी होगी।
साइमन बर्गोट

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

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

क्षमा करें दोस्तों, मुझे देर हो गई और जब मैंने उत्तर लिखा तो मैं बहुत थक गया था। मैंने यह कहने के लिए इसे फिर से लिखा है कि वास्तव में मेरा क्या मतलब है, जिसका क्रूट उत्परिवर्तन है।

13

और वर्गाकार आयत की अपेक्षा में स्क्वायर क्यों प्रयोग करने योग्य होगा?

क्योंकि यह एक उप-प्रकार होने का मतलब है (यह भी देखें: लिस्कोव प्रतिस्थापन सिद्धांत)। आप ऐसा करने में सक्षम होने के लिए कर सकते हैं:

Square s = new Square(5);
Rect r = s;
doSomethingWith(r); // written assuming a Rect, actually calls Square methods

OOP का उपयोग करते समय आप वास्तव में हर समय ऐसा करते हैं (कभी-कभी और भी अधिक स्पष्ट रूप से)।

और अगर हम स्क्वायर के लिए सेटविदथ और सेटहाइट तरीकों की सवारी करते हैं तो कोई समस्या क्यों होगी?

क्योंकि आप समझदारी से उन लोगों को ओवरराइड नहीं कर सकते Square। क्योंकि एक वर्ग "किसी आयत की तरह पुन: आकार नहीं ले सकता है"। जब आयत की ऊंचाई बदलती है, तो चौड़ाई समान रहती है। लेकिन जब एक वर्ग की ऊंचाई बदलती है, तो उसके अनुसार चौड़ाई बदलनी चाहिए। मुद्दा सिर्फ फिर से बड़े आकार का नहीं हो रहा है, यह स्वतंत्र रूप से दोनों आयामों में फिर से व्यवहार्य हो रहा है।


बहुत सारी भाषाओं में, आपको Rect r = s;लाइन की भी आवश्यकता नहीं है , आप बस doSomethingWith(s)और रनटाइम किसी sभी आभासी Squareतरीकों को हल करने के लिए किसी भी कॉल का उपयोग करेंगे ।
पैट्रिक एम

1
@PatrickM आपको किसी भी समझदार भाषा में इसकी आवश्यकता नहीं है जिसमें सबटाइपिंग हो। मैंने स्पष्ट करने के लिए उस लाइन को शामिल किया।

इसलिए ओवरराइड setWidthऔर setHeightचौड़ाई और ऊंचाई दोनों को बदलने के लिए।
ApproachingDarknessFish

@ValekHalfHeart यह ठीक विकल्प है जिस पर मैं विचार कर रहा हूं।

7
@ValekHalfHeart: यह वास्तव में Liskov प्रतिस्थापन सिद्धांत का उल्लंघन है जो आपको परेशान करेगा और आपको दो साल बाद एक अजीब बग खोजने की कोशिश कर कई रातों की नींद हराम करेगा जब आप भूल गए हैं कि कोड को कैसे काम करना चाहिए था।
Jan Hudec

9

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

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

समांतर चतुर्भुज एक चतुर्भुज है जिसमें दो समानांतर पैर होते हैं। इसमें दो जोड़े संयोजक कोण भी हैं। इन पंक्तियों के साथ एक समांतर चतुर्भुज वस्तु की कल्पना करना मुश्किल नहीं है:

class Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

आयत के बारे में सोचने का एक सामान्य तरीका समकोण के समान समांतर चतुर्भुज है। पहली नज़र में, यह आयत को Parallelogram से विरासत में एक अच्छा उम्मीदवार बनाने के लिए लग सकता है , ताकि आप उस सभी स्वादिष्ट कोड का पुन: उपयोग कर सकें। हालाँकि:

class Rectangle extends Parallelogram {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* BUG: Liskov violations ahead */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

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

अब, यह कैसे वर्गों और आयतों पर लागू होता है?

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

class Square extends Rectangle {
    function getSideA() {};
    function getSideB() {};
    function getAngleA() {};
    function getAngleB() {};

    /* BUG: More Liskov violations */
    function setSideA(newLength) {};
    function setSideB(newLength) {};

    /* Liskov violations inherited from Rectangle */
    function setAngleA(newAngle) {};
    function setAngleB(newAngle) {};
}

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

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


8

सबटाइपिंग व्यवहार के बारे में है।

टाइप Bका उपप्रकार होने के लिए A, उसे प्रत्येक ऑपरेशन का समर्थन करना चाहिए जो टाइप Aएक ही शब्दार्थ के साथ समर्थन करता है ("व्यवहार" के लिए फैंसी बात)। तर्क का उपयोग करना कि प्रत्येक बी एक काम नहीं करता है - व्यवहार संगतता को अंतिम कहना है। अधिकांश समय "बी एक प्रकार का ए" है, "बी के साथ ए जैसा व्यवहार करता है", लेकिन हमेशा नहीं

एक उदाहरण:

वास्तविक संख्याओं के सेट पर विचार करें। किसी भी भाषा में, हम उन्हें कार्य का समर्थन करने के लिए उम्मीद कर सकते हैं +, -, *, और /। अब सकारात्मक पूर्णांक ({1, 2, 3, ...}) के सेट पर विचार करें। स्पष्ट रूप से, प्रत्येक सकारात्मक पूर्णांक भी एक वास्तविक संख्या है। लेकिन क्या सकारात्मक पूर्णांक का प्रकार वास्तविक संख्याओं के प्रकार का एक उपप्रकार है? आइए चार ऑपरेशनों को देखें और देखें कि क्या सकारात्मक पूर्णांक वास्तविक संख्याओं के समान व्यवहार करते हैं:

  • +: हम समस्याओं के बिना सकारात्मक पूर्णांक जोड़ सकते हैं।
  • -: सकारात्मक पूर्णांकों के सभी घटावों के परिणामस्वरूप सकारात्मक पूर्णांक नहीं बनते हैं। जैसे 3 - 5
  • *: हम समस्याओं के बिना सकारात्मक पूर्णांक गुणा कर सकते हैं।
  • /: हम हमेशा सकारात्मक पूर्णांकों को विभाजित नहीं कर सकते हैं और एक सकारात्मक पूर्णांक प्राप्त कर सकते हैं। जैसे 5 / 3

इसलिए सकारात्मक पूर्णांक वास्तविक संख्याओं के सबसेट होने के बावजूद, वे उपप्रकार नहीं हैं। एक समान तर्क परिमित आकार के पूर्णांक के लिए बनाया जा सकता है। स्पष्ट रूप से प्रत्येक 32-बिट पूर्णांक 64-बिट पूर्णांक भी है, लेकिन 32_BIT_MAX + 1आपको प्रत्येक प्रकार के लिए अलग-अलग परिणाम देगा। इसलिए अगर मैंने आपको कुछ प्रोग्राम दिया और आपने प्रत्येक 32-बिट पूर्णांक चर के प्रकार को 64-बिट पूर्णांक में बदल दिया, तो एक अच्छा मौका है कि कार्यक्रम अलग तरह से व्यवहार करेगा (जिसका लगभग हमेशा गलत मतलब होता है )।

बेशक, आप +32-बिट इनट्स के लिए परिभाषित कर सकते हैं ताकि परिणाम 64-बिट पूर्णांक हो, लेकिन अब आपको हर बार दो 32-बिट संख्याओं को जोड़ने के लिए 64 बिट्स का स्थान आरक्षित करना होगा। यह आपकी स्मृति की जरूरतों के आधार पर आपको स्वीकार्य हो सकता है या नहीं।

यह बात क्यों है?

कार्यक्रमों का सही होना महत्वपूर्ण है। यकीनन यह एक कार्यक्रम के लिए सबसे महत्वपूर्ण संपत्ति है। यदि कोई प्रोग्राम किसी प्रकार के लिए सही है A, तो उस प्रोग्राम की गारंटी देने का एकमात्र तरीका कुछ उपप्रकार के लिए सही रहेगा B, यदि वह हर तरह से Bव्यवहार करता है A

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


6

यदि एक वर्ग आयत का एक प्रकार है, तो एक वर्ग आयत से क्यों नहीं बन सकता है? या यह एक बुरा डिजाइन क्यों है?

पहले चीजें पहले, अपने आप से पूछें कि आपको क्यों लगता है कि एक वर्ग एक आयत है।

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

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

तो इससे पहले कि आप यह भी कहें कि "एक वर्ग एक प्रकार का आयत है" आपको पहले खुद से पूछना होगा, क्या यह उन मानदंडों पर आधारित है जिनकी मुझे परवाह है

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

आप एक स्थैतिक प्रणाली को मॉडलिंग नहीं कर रहे हैं, आप एक गतिशील प्रणाली को मॉडलिंग कर रहे हैं जहां चीजें होने जा रही हैं (आकृतियों का निर्माण, विनाश, परिवर्तन, खींचना आदि) होने जा रहे हैं। इस संदर्भ में आप वस्तुओं के बीच साझा व्यवहार की परवाह करते हैं, क्योंकि आपकी प्राथमिक चिंता यह है कि आप एक आकृति के साथ क्या कर सकते हैं, अभी भी एक सुसंगत प्रणाली के लिए किन नियमों को बनाए रखना है।

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

किस मामले में उन्हें इस तरह से मॉडल न करें। आप क्यों? यह आपको एक अनावश्यक प्रतिबंध के अलावा और कुछ नहीं देता है।

यदि हम स्क्वायर ऑब्जेक्ट बनाते हैं, तो यह केवल प्रयोग करने योग्य होगा, और यदि हम स्क्वायर के लिए सेटविदथ और सेटहाइट तरीकों को ओवरराइड करते हैं तो कोई समस्या क्यों होगी?

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

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

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

इस सवाल के बहुत सारे उत्तर समूह व्यवहार के बारे में उप-टाइपिंग पर ध्यान केंद्रित करते हैं 'उन्हें नियम बनाते हैं

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


यदि प्रकार के चर का Rectangleउपयोग केवल मूल्यों का प्रतिनिधित्व करने के लिए किया जाता है , तो यह संभव है कि एक वर्ग के Squareलिए विरासत से Rectangleऔर उसके अनुबंध का पूरी तरह से पालन करना संभव हो । दुर्भाग्यवश, कई भाषाएं उन चरों के बीच कोई अंतर नहीं करती हैं जो मूल्यों को घेरते हैं और जो संस्थाओं की पहचान करते हैं।
सुपरकैट

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

या इसे दूसरे तरीके से करने के लिए: कोशिश न करें और चम्मच को मोड़ें। यह असंभव है। इसके बजाय केवल सच्चाई को महसूस करने की कोशिश करें, कि कोई चम्मच नहीं है। :-)
कॉर्मैक मुलहॉल

1
एक अपरिवर्तनीय Squareप्रकार का होना जो एक अपरिवर्तनीय प्रकार से विरासत में मिलता है Rectnagleयदि कुछ प्रकार के ऑपरेशन होते हैं जो केवल वर्गों पर ही किए जा सकते हैं। अवधारणा के एक यथार्थवादी उदाहरण के रूप में, एक ReadableMatrixप्रकार पर विचार करें [आधार प्रकार एक आयताकार सरणी है जिसे विभिन्न तरीकों से संग्रहीत किया जा सकता है, जिसमें बहुत कुछ शामिल है], और एक ComputeDeterminantविधि। यह ComputeDeterminantकेवल एक ReadableSquareMatrixप्रकार से काम करने के लिए समझ में आ सकता है ReadableMatrix, जिसे मैं एक Squareव्युत्पन्न का उदाहरण मानता हूं Rectangle
सुपरकैट

5

यदि एक वर्ग आयत का एक प्रकार है, तो एक वर्ग आयत से क्यों नहीं बन सकता है?

समस्या यह सोचने में निहित है कि अगर चीजें वास्तविकता में किसी तरह से संबंधित हैं, तो उन्हें मॉडलिंग के बाद बिल्कुल उसी तरह से संबंधित होना चाहिए।

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

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

  • वर्ग को आधार वर्ग के रूप में परिभाषित करें, widthपैरामीटर के साथ और resize(double factor)दिए गए कारक द्वारा चौड़ाई का आकार बदलना
  • आयत वर्ग और वर्ग के उपवर्ग को परिभाषित करें, क्योंकि यह एक और विशेषता जोड़ता है height, और इसके resizeकार्य को ओवरराइड करता है, जो कॉल करता है super.resizeऔर फिर दिए गए कारक द्वारा ऊंचाई का आकार बदलता है

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


+1 सिर्फ इसलिए कि एक वर्ग गणित में एक विशेष प्रकार की परिभाषा है, इसका मतलब यह नहीं है कि यह OO में समान है।
लविस

1
एक वर्ग एक वर्ग है और एक आयत एक आयत है। उनके बीच के रिश्तों को मॉडलिंग में भी पकड़ना चाहिए, या आपके पास एक बहुत खराब मॉडल है। असली मुद्दे इस प्रकार हैं: 1) यदि आप उन्हें परिवर्तनशील बनाते हैं, तो आप अब वर्गों और आयतों को मॉडलिंग नहीं कर रहे हैं; 2) यह मानते हुए कि सिर्फ इसलिए कि कुछ "एक" संबंध दो प्रकार की वस्तुओं के बीच है, आप एक को दूसरे के लिए अंधाधुंध रूप से प्रतिस्थापित कर सकते हैं।
डोभाल

4

क्योंकि एलएसपी द्वारा, दोनों के बीच वंशानुक्रम संबंध बनाना और ओवरराइड करना setWidthऔर setHeightयह सुनिश्चित करना कि दोनों समान रूप से भ्रमित और गैर-सहज व्यवहार का परिचय देते हैं। कहते हैं कि हमारे पास एक कोड है:

Rectangle r = createRectangle(); // create rectangle or square here
r.setWidth(10);
r.setHeight(20);
print(r.getWidth()); // expect to print 10
print(r.getHeight()); // expect to print 20

लेकिन अगर विधि createRectangleवापस आ गई Square, क्योंकि यह Squareविरासत से संभव है Rectange। फिर उम्मीदें टूट जाती हैं। यहाँ, इस कोड के साथ, हम उम्मीद करते हैं कि चौड़ाई या ऊँचाई क्रमशः चौड़ाई या ऊँचाई में परिवर्तन का कारण बनेगी। OOP की बात यह है कि जब आप सुपरक्लास के साथ काम करते हैं, तो आपको इसके तहत किसी भी उपवर्ग का शून्य ज्ञान होता है। और अगर उपवर्ग व्यवहार को बदल देता है ताकि यह अपेक्षाओं के खिलाफ हो जाए कि हमारे पास सुपरक्लास के बारे में है, तो इस बात की अधिक संभावना है कि कीड़े उत्पन्न होंगे। और इस तरह के कीड़े दोनों को डिबग करना और ठीक करना मुश्किल है।

OOP के बारे में प्रमुख विचारों में से एक यह है कि यह व्यवहार है, न कि विरासत में मिला डेटा (जो कि प्रमुख गलतफहमी IMO में से एक है)। और यदि आप वर्ग और आयत को देखते हैं, तो उनका खुद से कोई व्यवहार नहीं है कि हम विरासत के संबंध में संबंधित हो सकते हैं।


2

एलएसपी क्या कहता है कि कुछ भी जो विरासत में मिला है वह Rectangleहोना चाहिए Rectangle। अर्थात्, जो कुछ Rectangleकरता है, उसे करना चाहिए ।

संभवतः प्रलेखन के लिए Rectangleलिखा गया है कि Rectangleनाम rका व्यवहार इस प्रकार है:

r.setWidth(10);
r.setHeight(20);
print(r.getWidth());  // prints 10

यदि आपके स्क्वायर में वैसा ही व्यवहार नहीं है, तो यह एक जैसा व्यवहार नहीं करता है Rectangle। इसलिए एलएसपी का कहना है कि इसे विरासत में नहीं लेना चाहिए Rectangle। भाषा इस नियम को लागू नहीं कर सकती है, क्योंकि यह आपको एक विधि ओवरराइड में कुछ गलत करने से रोक नहीं सकता है, लेकिन इसका मतलब यह नहीं है कि "यह ठीक है क्योंकि भाषा मुझे विधियों को ओवरराइड करने देती है" इसे करने के लिए एक ठोस तर्क है!

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



@gnat: क्या आप मुझे इस संक्षिप्तता के लिए नीचे अन्य उत्तर को संपादित करने के लिए पसंद करेंगे? ;-) मुझे नहीं लगता कि मैं उन बिंदुओं को हटाए बिना कर सकता हूं जो अन्य उत्तर देने वाले को लगता है कि प्रश्न का उत्तर देने के लिए आवश्यक हैं और मुझे लगता है कि नहीं हैं।
स्टीव जेसप


1

उपप्रकार और, विस्तार द्वारा, OO प्रोग्रामिंग, अक्सर Liskov प्रतिस्थापन सिद्धांत पर भरोसा करते हैं, कि A के किसी भी मान का उपयोग किया जा सकता है जहां B की आवश्यकता होती है, यदि A <= B. यह OO आर्किटेक्चर में बहुत अधिक स्वयंसिद्ध है, अर्थात। यह माना जाता है कि सभी उपवर्गों के पास यह संपत्ति होगी (और यदि नहीं, तो उपप्रकार छोटी हैं और उन्हें ठीक करने की आवश्यकता है)।

हालांकि, यह पता चला है कि यह सिद्धांत या तो अधिकांश कोड के अवास्तविक / अप्रमाणिक है, या वास्तव में संतुष्ट करने के लिए असंभव है (गैर-तुच्छ मामलों में)! यह समस्या, जिसे वर्गाकार-आयत समस्या या वृत्त-दीर्घवृत्त समस्या ( http://en.wikipedia.org/wiki/Circle-ellipse_problem ) के रूप में जाना जाता है, इसे पूरा करना कितना कठिन है, इसका एक प्रसिद्ध उदाहरण है।

ध्यान दें कि हम अधिक-से-अधिक अवलोकन-समतुल्य वर्गों और आयतों को लागू कर सकते हैं , लेकिन केवल अधिक से अधिक कार्यक्षमता को तब तक फेंकने से जब तक कि अंतर व्यर्थ न हो।

एक उदाहरण के रूप में, http://okmij.org/ftp/Computation/Subtyping/ देखें

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