रूबी विरासत बनाम मिश्रण


127

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

मेरा प्रश्न: यदि आप कोड लिख रहे हैं जिसे बढ़ाया जाना चाहिए / उपयोगी होना शामिल है, तो आप इसे कभी कक्षा क्यों बनाएंगे? या दूसरा तरीका, आप इसे हमेशा एक मॉड्यूल क्यों नहीं बनायेंगे?

मैं केवल एक कारण के बारे में सोच सकता हूं कि आप कक्षा क्यों चाहते हैं, और यदि आपको कक्षा को तत्काल करने की आवश्यकता है तो ActiveRecord :: बेस के मामले में, आप इसे कभी भी सीधे नहीं भेजते। तो क्या इसके बजाय एक मॉड्यूल नहीं होना चाहिए था?

जवाबों:


176

मैं बस में इस विषय के बारे में पढ़ा अच्छी बुनियादी Rubyist (महान किताब, वैसे)। लेखक समझाने का एक बेहतर काम करता है जिससे मैं उसे उद्धृत करूँगा:


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

  • मॉड्यूल में उदाहरण नहीं हैं। यह निम्नानुसार है कि संस्थाएं या चीजें आमतौर पर कक्षाओं में सर्वश्रेष्ठ रूप से मॉडलिंग की जाती हैं, और संस्थाओं या चीजों की विशेषताओं या गुणों को मॉड्यूल में सबसे अच्छा समझाया जाता है। इसके विपरीत, जैसा कि खंड 4.1.1 में उल्लेख किया गया है, वर्ग के नाम संज्ञाएं हैं, जबकि मॉड्यूल नाम अक्सर विशेषण होते हैं (स्टैक बनाम स्टैकेल)।

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

इन नियमों को एक उदाहरण में सम्‍मिलित करते हुए, यहां आपको यह करना चाहिए:

module Vehicle 
... 
class SelfPropelling 
... 
class Truck < SelfPropelling 
  include Vehicle 
... 

बल्कि, आपको यह करना चाहिए:

module SelfPropelling 
... 
class Vehicle 
  include SelfPropelling 
... 
class Truck < Vehicle 
... 

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


1
उदाहरण इसे बड़े करीने से दिखाता है - ट्रक एक वाहन है - ऐसा कोई ट्रक नहीं है जो एक वाहन नहीं होगा।
पीएल जे

1
उदाहरण इसे बड़े करीने से दिखाता है - TruckIS A Vehicle- ऐसा कोई नहीं है Truckजो a नहीं होगा Vehicle। हालाँकि मैं शायद मॉड्यूल SelfPropelable(:?) Hmm को SelfPropeledसही कहता हूँ , लेकिन यह लगभग समान है: D। वैसे भी मैं में शामिल नहीं हैं Vehicle, लेकिन में Truck- के रूप में देखते हैं वाहन है कि नहीं हैं SelfPropeled। यह भी पूछना अच्छा संकेत है - क्या अन्य चीजें हैं, वाहन नहीं हैं जो हैं SelfPropeled? - अच्छा शायद, लेकिन मुझे खोजना मुश्किल होगा। इसलिए Vehicleसेल्फप्रोपेलिंग से वर्ग के रूप में विरासत में मिला (वर्ग के रूप में यह फिट नहीं होगा SelfPropeled- जैसा कि एक भूमिका से अधिक है)
पीएल जे

39

मुझे लगता है कि मिश्रण एक महान विचार है, लेकिन यहां एक और समस्या है जिसका किसी ने उल्लेख नहीं किया है: नेमस्पेस टकराव। विचार करें:

module A
  HELLO = "hi"
  def sayhi
    puts HELLO
  end
end

module B
  HELLO = "you stink"
  def sayhi
    puts HELLO
  end
end

class C
  include A
  include B
end

c = C.new
c.sayhi

कौन सी जीतता है? रूबी में, यह उत्तरार्द्ध हो जाता है module B, क्योंकि आपने इसे बाद में शामिल किया था module A। अब, यह इस समस्या से बचने के लिए आसान है: सुनिश्चित करें कि सभी के बनाने module Aऔर module Bके स्थिरांक और तरीकों की संभावना नहीं नामस्थान में हैं। समस्या यह है कि संकलक होने पर कंपाइलर आपको चेतावनी नहीं देता है।

मेरा तर्क है कि यह व्यवहार प्रोग्रामर की बड़ी टीमों के लिए नहीं है - आपको यह नहीं मानना ​​चाहिए कि लागू करने वाला व्यक्ति class Cहर नाम के बारे में जानता है। रूबी आपको एक अलग प्रकार के निरंतर या विधि को ओवरराइड करने देगा । मुझे यकीन नहीं है कि कभी भी सही व्यवहार माना जा सकता है।


2
यह सावधानी का एक बुद्धिमान शब्द है। C ++ में कई विरासत के नुकसान की याद दिलाता है।
क्रिस टोंकिंसन

1
क्या इसके लिए कोई अच्छा शमन है? यह एक कारण की तरह दिखता है कि क्यों पायथन मल्टीपल इनहेरिटेंस एक बेहतर समाधान है (भाषा पी * साइटिंग मैच शुरू करने की कोशिश नहीं कर रहा है; बस इस विशिष्ट विशेषता की तुलना करता है)।
मार्सिन

1
@bazz महान और सभी है, लेकिन अधिकांश भाषाओं में रचना बोझिल है। यह ज्यादातर डक-टंकित भाषाओं में भी प्रासंगिक है। यह भी गारंटी नहीं है कि आपको अजीब स्थिति नहीं मिलती है।
मार्सिन

पुरानी पोस्ट, मुझे पता है, लेकिन अभी भी खोजों में पता चला है। उत्तर आंशिक रूप से गलत है - C#sayhiआउटपुट B::HELLOइसलिए नहीं कि रूबी स्थिरांक को मिलाती है, बल्कि इसलिए कि रूबी स्थिरांक को दूर से करीब तक हल करता है - इसलिए HELLOसंदर्भित Bहमेशा के लिए हल हो जाएगा B::HELLO। यह तब भी धारण करता है जब वर्ग C परिभाषित करता है कि वह स्वयं C::HELLOभी है।
लास

13

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


2
यह सवाल का सीधा जवाब देता है: विरासत एक विशिष्ट संगठनात्मक संरचना को लागू करती है जो आपकी परियोजना को और अधिक पठनीय बना सकती है।
एमरी

10

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

और हाँ, उप-वर्ग द्वारा विस्तारित की बजाय ActiveRecord को शामिल किया जाना चाहिए। एक और ORM - datamapper - ठीक है कि प्राप्त!


4

मुझे एंडी गास्केल का जवाब बहुत पसंद है - बस यह जोड़ना चाहता था कि ActiveRecord को इनहेरिटेंस का उपयोग नहीं करना चाहिए, बल्कि एक मॉडल / वर्ग के व्यवहार (ज्यादातर दृढ़ता) को जोड़ने के लिए एक मॉड्यूल शामिल करना चाहिए। ActiveRecord बस गलत प्रतिमान का उपयोग कर रहा है।

इसी कारण से, मैं MongoMapper पर MongoId को बहुत पसंद करता हूं, क्योंकि यह डेवलपर को समस्या डोमेन में सार्थक कुछ मॉडलिंग के तरीके के रूप में विरासत का उपयोग करने का मौका छोड़ देता है।

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


1

मुझे लगता है कि सबसे अच्छा तरीका है मिक्सिन्स वर्चुअल क्लास के रूप में। मिक्सिन "वर्चुअल क्लास" हैं जिन्हें एक क्लास या मॉड्यूल की पूर्वज श्रृंखला में इंजेक्ट किया गया है।

जब हम "शामिल" का उपयोग करते हैं और इसे एक मॉड्यूल पास करते हैं, तो यह मॉड्यूल को पूर्वजों की श्रृंखला में उस वर्ग से ठीक पहले जोड़ता है, जिसे हम निम्नलिखित हैं:

class Parent
end 

module M
end

class Child < Parent
  include M
end

Child.ancestors
 => [Child, M, Parent, Object ...

रूबी की हर वस्तु में एक एकल वर्ग भी होता है। इस एकल वर्ग में जोड़े गए तरीकों को सीधे ऑब्जेक्ट पर बुलाया जा सकता है और इसलिए वे "क्लास" विधियों के रूप में कार्य करते हैं। जब हम किसी ऑब्जेक्ट पर "विस्तार" का उपयोग करते हैं और ऑब्जेक्ट को एक मॉड्यूल पास करते हैं, तो हम मॉड्यूल के तरीकों को ऑब्जेक्ट के सिंगलटन वर्ग में जोड़ रहे हैं:

module M
  def m
    puts 'm'
  end
end

class Test
end

Test.extend M
Test.m

हम सिंगलटन क्लास को सिंगलटन क्लास के साथ एक्सेस कर सकते हैं:

Test.singleton_class.ancestors
 => [#<Class:Test>, M, #<Class:Object>, ...

रूबी मॉड्यूल के लिए कुछ हुक प्रदान करता है जब उन्हें कक्षाओं / मॉड्यूल में मिलाया जाता है। includedरूबी द्वारा प्रदान की जाने वाली एक हुक विधि है जिसे तब भी कहा जाता है जब आप किसी मॉड्यूल या कक्षा में एक मॉड्यूल शामिल करते हैं। शामिल की तरह, extendedविस्तार के लिए एक जुड़ा हुआ हुक है। यह तब कहा जाएगा जब एक मॉड्यूल दूसरे मॉड्यूल या वर्ग द्वारा बढ़ाया जाता है।

module M
  def self.included(target)
    puts "included into #{target}"
  end

  def self.extended(target)
    puts "extended into #{target}"
  end
end

class MyClass
  include M
end

class MyClass2
  extend M
end

यह एक दिलचस्प पैटर्न बनाता है जिसे डेवलपर्स उपयोग कर सकते हैं:

module M
  def self.included(target)
    target.send(:include, InstanceMethods)
    target.extend ClassMethods
    target.class_eval do
      a_class_method
    end
  end

  module InstanceMethods
    def an_instance_method
    end
  end

  module ClassMethods
    def a_class_method
      puts "a_class_method called"
    end
  end
end

class MyClass
  include M
  # a_class_method called
end

जैसा कि आप देख सकते हैं, यह एकल मॉड्यूल उदाहरण विधियों, "वर्ग" विधियों को जोड़ रहा है, और लक्ष्य वर्ग पर सीधे कार्य कर रहा है (इस मामले में a_class_method () को कॉल कर रहा है)।

ActiveSupport :: चिंता इस पैटर्न encapsulates। ActiveSupport का उपयोग करने के लिए यहां एक ही मॉड्यूल फिर से लिखा गया है :: चिंता:

module M
  extend ActiveSupport::Concern

  included do
    a_class_method
  end

  def an_instance_method
  end

  module ClassMethods
    def a_class_method
      puts "a_class_method called"
    end
  end
end

-1

अभी, मैं templateडिजाइन पैटर्न के बारे में सोच रहा हूं । यह सिर्फ एक मॉड्यूल के साथ सही नहीं लगेगा।

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