क्या कोई भाषा या डिज़ाइन पैटर्न है जो एक वर्ग पदानुक्रम में वस्तु व्यवहार या गुणों के * निष्कासन * की अनुमति देता है?


28

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

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

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

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

तो मेरा प्रश्न "मैं एक विशेषता को कैसे लागू कर सकता हूं?" यदि आपकी सुपर-क्लास एक जावा सीरियल है, तो आपको भी एक होना होगा, भले ही आपके लिए अपने राज्य को क्रमबद्ध करने के लिए कोई रास्ता न हो, उदाहरण के लिए यदि आपने "सॉकेट" शामिल किया हो।

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


3
'हर जगह कुछ भयानक हैक का उपयोग किए बिना': एक व्यवहार को निष्क्रिय करना एक भयानक हैक है: यह ऐसा होगा function save_yourself_from_crashing_airplane(Bird b) { f.fly() }जो बहुत अधिक जटिल हो जाएगा। (जैसा कि पीटर टॉर्क ने कहा, यह एलएसपी का उल्लंघन करता है)
किपला

रणनीति पैटर्न और वंशानुक्रम का एक संयोजन आपको विशिष्ट सुपर प्रकारों के लिए विरासत में मिला "व्यवहार" करने की अनुमति दे सकता है? जब आप कहते हैं: " it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere"क्या आप व्यवहार को नियंत्रित करने वाली एक फैक्ट्री पद्धति पर विचार करते हैं?
स्टॉपरयूज़र

1
एक कोर्स सिर्फ एक NotSupportedExceptionसे फेंक सकता है Penguin.fly()
फेलिक्स डॉमबेक

जहाँ तक भाषाएँ जाती हैं, आप निश्चित रूप से एक विधि को चाइल्ड क्लास में लागू कर सकते हैं । उदाहरण के लिए, रूबी में class Penguin < Bird; undef fly; end;:। आपको एक और सवाल करना चाहिए या नहीं ।
नाथन लॉन्ग

यह लिस्कॉव सिद्धांत को तोड़ देगा और यकीनन ओओपी का पूरा बिंदु होगा।
डेडलिंक

जवाबों:


17

जैसा कि दूसरों ने उल्लेख किया है कि आपको एलएसपी के खिलाफ जाना होगा।

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

यह तार्किक अर्थ लगा सकता है, बल्कि फिर पेंगुइन को पक्षी कहना है । आपका कहना है पेंगुइन बर्ड से व्यवहार के कुछ सबसेट विरासत में मिला है।

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

var Penguin = Object.create(Bird);
Penguin.fly = undefined;
Penguin.swim = function () { ... };

इस विशेष मामले में, वस्तु के मूल्य के साथ एक संपत्ति लिखकर विरासत में मिली विधि Penguinको सक्रिय रूप से छायांकित कर रहा है ।Bird.flyflyundefined

अब आप कह सकते हैं कि अब Penguinइसे सामान्य Birdनहीं माना जा सकता है। लेकिन जैसा कि उल्लेख किया गया है, वास्तविक दुनिया में यह बस नहीं हो सकता। क्योंकि हम Birdएक उड़ान इकाई होने के नाते मॉडलिंग कर रहे हैं ।

विकल्प यह है कि बर्ड्स उड़ सकता है व्यापक धारणा नहीं है। यह एक समझदारी होगी Birdजो सभी पक्षियों को बिना किसी असफलता के इसे प्राप्त करने की अनुमति देता है। इसका मतलब केवल यह धारणा बनाना है कि सभी उपवर्ग धारण कर सकते हैं।

आम तौर पर मिक्सिन के विचार यहाँ अच्छी तरह से लागू होते हैं। एक बहुत पतला आधार वर्ग रखें, और इसमें अन्य सभी व्यवहार को मिलाएं।

उदाहरण:

// for some value of Object.make
var Penguin = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Swimmer, ...
);
var Hawk = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Flyer, Carnivore, ...
);

यदि आपका जिज्ञासु है, तो मेरा कार्यान्वयन हैObject.make

इसके अलावा:

तो मेरा प्रश्न "मैं एक विशेषता को कैसे लागू कर सकता हूं?" यदि आपकी सुपर-क्लास एक जावा सीरियल है, तो आपको भी एक होना होगा, भले ही आपके लिए अपने राज्य को क्रमबद्ध करने के लिए कोई रास्ता न हो, उदाहरण के लिए यदि आपने "सॉकेट" शामिल किया हो।

आप एक विशेषता को "अन-इम्प्लीमेंट" नहीं करते हैं। आप बस अपने वंशानुक्रम को निर्धारित करते हैं। या तो आप अपने सुपर क्लास कॉन्ट्रैक्ट को पूरा कर सकते हैं या आपको उस प्रकार के अपने बहाने नहीं बनाने चाहिए।

यह वह जगह है जहाँ वस्तु रचना चमकती है।

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

आपको "NotX" विशेषता का उपयोग नहीं करना चाहिए। यह सिर्फ भयावह कोड ब्लोट है। यदि कोई फ़ंक्शन किसी उड़ने वाली वस्तु की उम्मीद करता है, तो उसे दुर्घटनाग्रस्त होने पर उसे क्रैश और जलना चाहिए।


10
"वास्तविक दुनिया में यह बस नहीं कर सकता।" हाँ यह कर सकते हैं। एक पेंगुइन एक पक्षी है। उड़ने की क्षमता पक्षी की संपत्ति नहीं है, यह केवल पक्षियों की अधिकांश प्रजातियों का एक संयोग है। पक्षियों को परिभाषित करने वाले गुण "पंख वाले, पंख वाले, द्विपाद, एंडोथर्मिक, अंडे देने वाले, कशेरुक जानवर" (विकिपीडिया) हैं - वहाँ उड़ान के बारे में कुछ भी नहीं।
pdr

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

2
@ रेयानोस: पेंगुइन वास्तव में पंख वाले हैं। उनके पंख निश्चित रूप से काफी छोटे और घने हैं।
जॉन पूर्डी

@ जोंपूर्डी मेला काफी, मुझे हमेशा लगता है कि उनके पास फर था।
रेयनोस

सामान्य रूप से +1, और विशेष रूप से "मैमथ" के लिए। जबरदस्त हंसी!
सेबस्टियन डायट

28

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

कुछ भाषाएं अधिक लचीले तरीके से ऐसी समस्याओं से निपटने के लिए लक्षण या मिश्रण प्रदान करती हैं


1
एलएसपी प्रकारों के लिए है , कक्षाओं के लिए नहीं ।
जोर्ग डब्ल्यू मित्तग

2
@ PéterTörök: यह प्रश्न अन्यथा मौजूद नहीं होगा :-) मैं रूबी से दो उदाहरणों के बारे में सोच सकता हूं। Classका एक उपवर्ग है Moduleभले ही ClassIS-नहीं-ए Module। लेकिन यह अभी भी एक उपवर्ग होने के लिए समझ में आता है, क्योंकि यह बहुत सारे कोड का पुन: उपयोग करता है। OTOH, StringIOIS-A IO, लेकिन दोनों का कोई वंशानुक्रम संबंध नहीं है (इसके अलावा, दोनों के विरासत से स्पष्ट Object, निश्चित रूप से), क्योंकि वे किसी भी कोड को साझा नहीं करते हैं। कक्षाएं कोड साझा करने के लिए हैं, प्रकार प्रोटोकॉल का वर्णन करने के लिए हैं। IOऔर StringIOएक ही प्रोटोकॉल है, इसलिए एक ही प्रकार, लेकिन उनके वर्ग असंबंधित हैं।
जॉर्ज डब्ल्यू मित्तग

1
@ JörgWMittag, ठीक है, अब मैं बेहतर समझती हूं कि आपका क्या मतलब है। हालाँकि, मेरे लिए आपका पहला उदाहरण कुछ मौलिक समस्या की अभिव्यक्ति की तुलना में विरासत के दुरुपयोग की तरह लगता है, जो आप सुझाते हैं। पब्लिक इनहेरिटेंस IMO का उपयोग कार्यान्वयन को फिर से उपयोग करने के लिए नहीं किया जाना चाहिए, केवल उपप्रकार संबंधों (इस-ए) को व्यक्त करने के लिए। और यह तथ्य कि इसका दुरुपयोग किया जा सकता है, यह अयोग्य नहीं है - मैं किसी भी डोमेन से किसी भी प्रयोग करने योग्य उपकरण की कल्पना नहीं कर सकता जिसका दुरुपयोग नहीं किया जा सकता।
पेटर तोर्क

2
इस उत्तर को लोगों तक पहुंचाने के लिए: ध्यान दें कि यह वास्तव में सवाल का जवाब नहीं देता है, खासकर संपादित स्पष्टीकरण के बाद। मुझे नहीं लगता कि यह उत्तर एक पतन के योग्य है, क्योंकि वह जो कहता है वह जानना बहुत सही और महत्वपूर्ण है, लेकिन उसने वास्तव में इस सवाल का जवाब नहीं दिया।
jhocking

1
एक जावा की कल्पना करें जिसमें केवल इंटरफेस प्रकार हैं, कक्षाएं नहीं हैं, और उपवर्ग उनके सुपरक्लास के इंटरफेस को "अन-इम्प्लीमेंट" करने में सक्षम हैं, और आपके पास एक मोटा विचार है, मुझे लगता है।
जोर्ग डब्ल्यू मित्तग

15

Fly()पहले उदाहरण में है: द स्ट्रैटेजी पैटर्न के लिए हेड फर्स्ट डिज़ाइन पैटर्न , और यह एक अच्छी स्थिति है कि आपको "इनहेरिटेंस पर अनुकूल रचना" क्यों करनी चाहिए

आप सुपरपोर्टेप्स होने से कंपोजिशन और इनहेरिटेंस को मिक्स कर सकते हैं FlyingBird, FlightlessBirdजिसमें किसी फैक्ट्री द्वारा इंजेक्ट किया गया सही व्यवहार Penguin : FlightlessBirdहो, जैसे कि संबंधित सबटाइप्स स्वचालित रूप से प्राप्त होते हैं, और वास्तव में विशिष्ट कुछ भी निश्चित रूप से फैक्ट्री द्वारा संभाला जाता है।


1
मैंने अपने उत्तर में डेकोरेटर पैटर्न का उल्लेख किया, लेकिन रणनीति पैटर्न बहुत अच्छी तरह से काम करता है।
jhocking

1
+1 "विरासत पर अनुकूल रचना।" हालांकि, सांख्यिकीय रूप से टाइप की गई भाषाओं में रचना को लागू करने के लिए विशेष डिजाइन पैटर्न की आवश्यकता ने रूबी जैसी गतिशील भाषाओं के प्रति मेरे पूर्वाग्रह को पुष्ट किया।
रॉय टिंकर

11

क्या वास्तविक समस्या यह नहीं है कि आप Birdएक Flyविधि मान रहे हैं ? क्यों नहीं:

class Bird
{
    // features that all birds have
}

class BirdThatCanSwim : Bird
{
    public void Swim() {...};
}

class BirdThatCanFly : Bird
{
    public void Fly() {...};
}


class Penguin : BirdThatCanSwim { }
class Sparrow : BirdThatCanFly { }

अब स्पष्ट समस्या मल्टीपल इनहेरिटेंस ( Duck) है, इसलिए आपको वास्तव में क्या चाहिए इंटरफेस:

interface IBird { }
interface IBirdThatCanSwim : IBird { public void Swim(); }
interface IBirdThatCanFly : IBird { public void Fly(); }
interface IBirdThatCanQuack : IBird { public void Quack(); }

class Duck : BirdThatCanFly, IBirdThatCanSwim, IBirdThatCanQuack
{
    public void Swim() {...};
    public void Quack() {...};
}

3
समस्या यह है कि विकास Liskov प्रतिस्थापन सिद्धांत का पालन नहीं करता है, और सुविधा हटाने के साथ विरासत करता है।
डोनाल्ड फेलो

7

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

लेकिन जैसा कि Péter Török ने कहा, यह LSP का उल्लंघन करेगा


इस भाग में, मैं एलएसपी के बारे में भूल जाऊंगा, और मान लूंगा कि:

  • पक्षी एक वर्ग है जिसमें मक्खी () पद्धति होती है
  • पेंगुइन को बर्ड से विरासत में मिला होगा
  • पेंगुइन उड़ नहीं सकता ()
  • मुझे परवाह नहीं है कि यह एक अच्छा डिज़ाइन है या यदि यह वास्तविक दुनिया से मेल खाता है, क्योंकि यह इस प्रश्न में प्रदान किया गया उदाहरण है।

तुमने कहा था :

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

ऐसा लगता है कि आप जो चाहते हैं वह है पायथन का " अनुमति के बजाय क्षमा मांगना "

बस अपना पेंग्विन एक अपवाद फेंकें या नॉनफेंसिंगबर्ड क्लास से वारिस करें जो एक अपवाद (छद्म कोड) फेंकता है:

class Penguin extends Bird {
     function fly():void {
          throw new Exception("Hey, I'm a penguin, I can't fly !");
     }
}

वैसे, आप जो भी चुनते हैं: एक अपवाद को बढ़ाते हुए या किसी विधि को हटाते हुए, अंत में, निम्नलिखित कोड (यह मानते हुए कि आपकी भाषा हटाने का समर्थन करती है):

var bird:Bird = new Penguin();
bird.fly();

एक क्रम अपवाद को फेंक देगा।


"बस अपने पेंगुइन को एक अपवाद फेंक दें या नॉनफेंसिंगबर्ड क्लास से विरासत में मिला जो एक अपवाद फेंकता है" जो अभी भी एलएसपी का उल्लंघन है। यह अभी भी सुझाव दे रहा है कि एक पेंगुइन उड़ सकता है, भले ही इसका मक्खी का कार्यान्वयन विफल हो। पेंगुइन पर एक मक्खी विधि कभी नहीं होनी चाहिए।
पीडीआर

@pdr: यह सुझाव नहीं दे रहा है कि एक पेंगुइन उड़ सकता है, लेकिन यह उड़ना चाहिए (यह एक अनुबंध है)। अपवाद आपको बताएगा कि यह नहीं हो सकता । वैसे, मैं यह दावा नहीं कर रहा हूं कि यह एक अच्छा ओओपी अभ्यास है, मैं सिर्फ एक सवाल का एक जवाब दे रहा हूं
डेविड

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

@pdr "मुझे आपके संस्करण में एक कोशिश / पकड़ का उपयोग करना है" -> यह अनुमति के बजाय क्षमा मांगने का पूरा बिंदु है (क्योंकि यहां तक ​​कि एक बतख भी अपने पंख तोड़ सकता था और उड़ने में सक्षम नहीं था)। मैं शब्दांकन ठीक कर दूँगा।
डेविड

"यह अनुमति के बजाय क्षमा मांगने का पूरा बिंदु है।" हाँ, सिवाय इसके कि यह फ्रेमवर्क किसी भी लापता विधि के लिए एक ही प्रकार के अपवाद को फेंकने की अनुमति देता है, इसलिए पायथन की "कोशिश: एट्रीब्यूटरी को छोड़कर:" बिल्कुल C # s के बराबर है अगर (X Y है तो) {} और {} "" और तुरंत पहचानने योग्य जैसे की। लेकिन अगर आपने जानबूझकर बर्ड में डिफ़ॉल्ट मक्खी () कार्यक्षमता को ओवरराइड करने के लिए एक CannotFlyException फेंक दिया तो यह कम पहचानने योग्य हो जाता है।
पीडीआर

7

जैसा कि किसी ने टिप्पणी में ऊपर बताया, पेंगुइन पक्षी हैं, पेंगुइन उड़ते नहीं हैं, सभी पक्षी उड़ नहीं सकते हैं।

तो बर्ड.फ्लाई () मौजूद नहीं होना चाहिए या काम नहीं करने देना चाहिए। मैं पूर्व को पसंद करता हूं।

फ्लाइंगबर्ड फैली होने से बर्ड के पास एक (।) तरीका सही होगा, ज़ाहिर है।


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

6

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

तो, इसके बजाय:

class Bird {
public:
   virtual void fly()=0;
};

आपके पास ऐसा कुछ होना चाहिए:

   class Bird {
   public:
      virtual float fly(float x) const=0;
   };

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

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

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


1
मुझे आपका जवाब काफी पसंद है। मुझे लगता है कि यह अधिक वोटों का हकदार है।
सेबेस्टियन डायट

2
बहुत बढ़िया जवाब। समस्या यह है कि सवाल केवल अपने हाथों को तरंगित करता है जैसे कि वास्तव में क्या उड़ता है ()। मक्खी का कोई वास्तविक कार्यान्वयन बहुत कम से कम, एक गंतव्य - मक्खी (समन्वय गंतव्य) होगा जो पेंगुइन के मामले में, {return currentPosition) को लागू करने के लिए ओवरराइड किया जा सकता है}}
क्रिस कूडमोर

4

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

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


3

पीटर ने लिसकोव प्रतिस्थापन सिद्धांत का उल्लेख किया है, लेकिन मुझे लगता है कि समझाने की आवश्यकता है।

मान लें कि क्ष (x) प्रकार की वस्तुओं के बारे में साबित हो सकता है, जो कि टाइप T का है। तब q (y) को S के उन वस्तुओं के लिए सिद्ध होना चाहिए, जहां S, T का उप-प्रकार है।

इस प्रकार, यदि एक बर्ड (टाइप टी का ऑब्जेक्ट x) उड़ सकता है (q (x)) तो एक पेंगुइन (टाइप Y का ऑब्जेक्ट Y) उड़ सकता है (q (y)), परिभाषा के अनुसार। लेकिन यह स्पष्ट रूप से ऐसा नहीं है। ऐसे अन्य जीव भी हैं जो उड़ सकते हैं लेकिन पक्षी के प्रकार नहीं हैं।

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

लेकिन सुपरक्लास की प्रत्येक संपत्ति उसके सभी उपवर्गों पर लागू होनी चाहिए।

[प्रतिक्रिया में संपादित करने के लिए]

बर्ड के लिए CanFly का "लक्षण" लागू करना बेहतर नहीं है। यह अभी भी कॉलिंग कोड का सुझाव दे रहा है कि सभी पक्षी उड़ सकते हैं।

आपके द्वारा परिभाषित शर्तों में एक विशेषता बिलकुल यही है कि लिस्कोव का अर्थ था जब उसने "संपत्ति" कहा था।


2

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

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

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


0

ऐसी स्थिति को संभालने का विशिष्ट तरीका यह है कि आप UnsupportedOperationException(जावा) सम्मान की तरह कुछ फेंक दें । NotImplementedException(सी#)।


जब तक आप बर्ड में इस संभावना को दस्तावेज करते हैं।
डीजेकेवर्थ

0

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

०) "स्टैटिक टाइपिंग" को न मानें (जब मैंने पूछा तो मैंने किया, क्योंकि मैं जावा लगभग विशेष रूप से करता हूं)। मूल रूप से, समस्या एक भाषा के उपयोग पर निर्भर करती है।

1) किसी को डिजाइन में और एक के सिर में कोड-पुन: उपयोग पदानुक्रम से टाइप-पदानुक्रम को अलग करना चाहिए, भले ही वे ज्यादातर ओवरलैप हों। आम तौर पर, पुन: उपयोग के लिए कक्षाओं का उपयोग करें, और प्रकारों के लिए इंटरफेस।

2) आमतौर पर बर्ड आईएस आईएस-ए फ्लाई का कारण यह है कि अधिकांश पक्षी उड़ सकते हैं, इसलिए यह कोड-पुन: उपयोग बिंदु-से-दृश्य से व्यावहारिक है, लेकिन यह कहना कि बर्ड आईएस-ए फ्लाई वास्तव में गलत है क्योंकि कम से कम एक अपवाद है (पेंगुइन)।

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

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

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

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

7) अपने फ्लाई विशेषता को इस तरह परिभाषित करें कि यह किसी ऐसी चीज के लिए एक स्पष्ट रास्ता प्रदान करता है जो उड़ नहीं सकती है। Fly.getNumberOfWings () 0 लौटा सकता है। यदि फ्लाई (दिशा, करंट प्लॉटपिनियन) को उड़ान के बाद नई स्थिति में लौटना चाहिए, तो पेंगुइन () की तुलना में यह परिवर्तन किए बिना केवल वर्तमान स्थिति वापस कर सकता है। आप तकनीकी रूप से काम करने वाले कोड के साथ एंड-अप कर सकते हैं, लेकिन कुछ कैविएट हैं। सबसे पहले, कुछ कोड में एक स्पष्ट "कुछ भी नहीं" व्यवहार नहीं हो सकता है। इसके अलावा, अगर कोई x.fly () को कॉल करता है, तो वे अपेक्षा करेंगे कि वह कुछ करे , भले ही टिप्पणी कहे कि मक्खी () को कुछ भी करने की अनुमति है । अंत में, पेंगुइन आईएस-ए फ्लाइंग अभी भी सही लौटेगा, जो प्रोग्रामर के लिए भ्रमित हो सकता है।

8) 5 के रूप में करो), लेकिन उन मामलों को प्राप्त करने के लिए रचना का उपयोग करें जिनके लिए कई उत्तराधिकार की आवश्यकता होगी। यह वह विकल्प है जिसे मैं एक स्थिर भाषा के लिए पसंद करूंगा, क्योंकि 6) अधिक बोझिल लगता है (और शायद अधिक मेमोरी की आवश्यकता है क्योंकि हमारे पास अधिक ऑब्जेक्ट हैं)। एक गतिशील भाषा 6) कम बोझिल बना सकती है, लेकिन मुझे संदेह है कि यह 5 से कम बोझिल हो जाएगा)।


0

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

यहां तक ​​कि पेंगुइन उड़ते हैं, शून्य ऊंचाई पर बर्फ के पार इसकी ग्लाइडिंग!

उड़ान के व्यवहार को आवश्यक रूप से ओवरराइड किया जा सकता है।

एक अन्य संभावना फ्लाई इंटरफ़ेस है। सभी पक्षी उस इंटरफ़ेस को लागू नहीं करेंगे।

class eagle : bird, IFly
class penguin : bird

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


-1

मुझे लगता है कि आप जिस पैटर्न की तलाश कर रहे हैं वह अच्छा पुराना बहुरूपता है। हालांकि आप कुछ भाषाओं में एक वर्ग से एक इंटरफ़ेस को हटाने में सक्षम हो सकते हैं, यह शायद Péter Török द्वारा दिए गए कारणों के लिए एक अच्छा विचार नहीं है। किसी भी ओओ भाषा में, हालांकि, आप इसके व्यवहार को बदलने के लिए एक विधि को ओवरराइड कर सकते हैं, और इसमें कुछ भी नहीं करना शामिल है। अपना उदाहरण उधार लेने के लिए, आप एक पेंगुइन :: मक्खी () विधि प्रदान कर सकते हैं जो निम्नलिखित में से कोई भी करती है:

  • कुछ भी तो नहीं
  • एक अपवाद फेंकता है
  • इसके बजाय पेंगुइन :: तैरना () विधि कहता है
  • यह दावा करता है कि पेंगुइन पानी के भीतर है (वे पानी के माध्यम से "उड़ना" करते हैं)

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

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

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


2
यहां तक ​​कि पेंगुइन को बुलाने का सुझाव देने के लिए :: तैरना ()। यह कम से कम विस्मय के सिद्धांत का उल्लंघन करता है और आपके नाम को शाप देने के लिए हर जगह रखरखाव प्रोग्रामर का कारण बनेगा।
डीजेकेवर्थ

1
@DJClayworth उदाहरण के रूप में पहली जगह में हास्यास्पद पक्ष पर था, मक्खी () और तैरने () के हीन व्यवहार के उल्लंघन के लिए downvoting थोड़ा बहुत लगता है। लेकिन अगर आप वास्तव में इसे गंभीरता से देखना चाहते हैं, तो मैं सहमत हूं कि यह अधिक संभावना है कि आप दूसरे रास्ते पर जाएं और मक्खी () के संदर्भ में तैरना () लागू करें। अपने पैरों को पैड करके तैरते हुए बतख; पेंगुइन अपने पंख फड़फड़ा कर तैरते हैं।
कालेब

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

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

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