कई छोटे वर्ग बनाम तार्किक (लेकिन) जटिल विरासत


24

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

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

क्या उन्हें "बेहतर" पठनीयता प्राप्त करने के लिए, उन्हें अलग-अलग कक्षाओं में रखना बेहतर होगा, लेकिन कई कोड पुनरावृत्तियों को छोड़ दें, या अधिक DRY होने के लिए वंशानुक्रम का उपयोग करना बेहतर होगा?

वंशानुक्रम का उपयोग करके, आप कुछ इस तरह से समाप्त हो जाएंगे, लेकिन वर्ग के नाम इसे प्रासंगिक अर्थ खो देंगे (क्योंकि-ए, नहीं है-ए):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

मुझे पता है कि इनहेरिटेंस कोड पुन: उपयोग के लिए नहीं है और इसका उपयोग नहीं किया जाना चाहिए, लेकिन मुझे इस तरह के मामले में कोड पुनरावृत्ति को कम करने का कोई अन्य संभव तरीका नहीं दिख रहा है।

जवाबों:


58

सामान्य नियम "उत्तराधिकार पर प्रतिनिधिमंडल को प्राथमिकता देता है" पढ़ता है, न कि "सभी उत्तराधिकार से बचें"। यदि वस्तुओं का तार्किक संबंध है और बी का उपयोग ए से कहीं भी किया जा सकता है, तो विरासत का उपयोग करना अच्छा है। हालाँकि, यदि ऑब्जेक्ट्स समान नाम वाले फ़ील्ड्स के होते हैं, और जिनका कोई डोमेन संबंध नहीं है, तो वंशानुक्रम का उपयोग न करें।

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


निश्चित रूप से, परिवर्तन का कारण अच्छी बात है, लेकिन क्या स्थिति है, जहां ये कक्षाएं हैं और हमेशा स्थिर रहेंगी?
user969153

20
@ user969153: जब कक्षाएं वास्तव में स्थिर होंगी, तो कोई फर्क नहीं पड़ता। सभी अच्छे सॉफ्टवेयर इंजीनियरिंग भविष्य के अनुचर के लिए हैं, जिन्हें परिवर्तन करने की आवश्यकता है। यदि भविष्य में कोई बदलाव नहीं होगा, तो सब कुछ चला जाता है, लेकिन मुझ पर विश्वास करें, आपके पास हमेशा बदलाव होंगे जब तक आप ट्रैशपाइल के लिए कार्यक्रम नहीं करते।
thiton

@ user969153: कहा जा रहा है कि, "परिवर्तन का कारण" एक अनुमान है। यह आपको निर्णय लेने में मदद करता है, इससे ज्यादा कुछ नहीं।
thiton

2
अच्छा उत्तर। बदलने का कारण चाचा बॉब के एसओएलआईडी एस में संक्षिप्त रूप है जो अच्छे सॉफ्टवेयर डिजाइन करने में मदद करेगा।
क्ले

4
@ user969153, मेरे अनुभव में, "जहां ये कक्षाएं हैं और हमेशा स्थिर रहेंगी?" वैध उपयोग का मामला नहीं है :) मुझे अभी तक एक सार्थक आकार के अनुप्रयोग पर काम करना है जो पूरी तरह से अप्रत्याशित परिवर्तन का अनुभव नहीं करता था।
cdkMoose

18

वंशानुक्रम का उपयोग करके, आप कुछ इस तरह से समाप्त हो जाएंगे, लेकिन वर्ग के नाम इसे प्रासंगिक अर्थ खो देंगे (क्योंकि-ए, नहीं है-ए):

ठीक ठीक। इसे देखें, संदर्भ से बाहर:

Class D : C
{
    String age;
}

D के पास कौन से गुण हैं? क्या आप याद कर सकते हैं? क्या होगा यदि आप पदानुक्रम को और अधिक जटिल करते हैं:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

और फिर क्या, अगर, भयावहता पर, आप एक छवि जोड़ना चाहते हैं F पर फ़ील्ड नहीं तो E? आप इसे C और E से प्राप्त नहीं कर सकते।

मैंने एक वास्तविक स्थिति देखी है जहाँ बहुत सारे विभिन्न प्रकार के कंपोनेंट में एक नाम, एक आईडी और एक विवरण था। लेकिन यह उनके घटक होने की प्रकृति से नहीं था, यह सिर्फ एक संयोग था। प्रत्येक के पास एक आईडी थी क्योंकि वे एक डेटाबेस में संग्रहीत थे। नाम और विवरण प्रदर्शन के लिए थे।

इसी तरह के कारणों के लिए उत्पादों में एक आईडी, नाम और विवरण भी था। लेकिन वे घटक नहीं थे, न ही घटक उत्पाद थे। आप कभी भी दो चीजों को एक साथ नहीं देख पाएंगे।

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

मैं इसे पर्याप्त रूप से पर्याप्त नहीं कह सकता: यह एक अच्छा विचार नहीं है।

DRY आम गुणों की आवश्यकता को दूर करने के बारे में नहीं है। यदि कोई वस्तु उत्पाद नहीं है, तो उसे उत्पाद मत कहो। यदि कोई उत्पाद वर्णन नहीं करता है, तो उसके उत्पाद होने की प्रकृति से, उत्पाद के गुण के रूप में विवरण को परिभाषित नहीं करते हैं।

यह अंततः हुआ, कि हमारे पास बिना विवरण के घटक थे। इसलिए हम उन वस्तुओं में अशक्त होने लगे। अशक्त नहीं। शून्य। हमेशा। किसी भी प्रकार के उत्पाद के लिए X ... या बाद में Y ... और N।

यह Liskov प्रतिस्थापन सिद्धांत का एक गंभीर उल्लंघन है।

DRY अपने आप को कभी भी ऐसी स्थिति में रखने के बारे में नहीं है जहाँ आप एक जगह पर एक बग को ठीक कर सकते हैं और इसे कहीं और करना भूल सकते हैं। यह होने वाला नहीं है क्योंकि आपके पास एक ही संपत्ति के साथ दो ऑब्जेक्ट हैं। कभी नहीँ। तो इसके बारे में चिंता मत करो।

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


4

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

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


3

क्या यह इंटरफेस नहीं है? विभिन्न कार्यों के साथ असंबंधित वर्ग लेकिन समान संपत्ति के साथ।

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;

1

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

यहाँ इस बात का उदाहरण दिया गया है कि इसे किस तरह से एकाधिक वंशानुक्रम का उपयोग करके हल किया जा सकता है।

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.