रूबी में एक अमूर्त वर्ग को कैसे लागू किया जाए?


121

मुझे पता है कि माणिक में अमूर्त वर्ग की कोई अवधारणा नहीं है। लेकिन अगर इसे लागू करने की आवश्यकता है, तो इसके बारे में कैसे जाना जाए? मैंने कुछ इस तरह की कोशिश की ...

class A
  def self.new
    raise 'Doh! You are trying to write Java in Ruby!'
  end
end

class B < A
  ...
  ...
end

लेकिन जब मैं बी को तत्काल करने की कोशिश करता हूं, तो यह आंतरिक रूप से कॉल करने जा रहा है A.newजो अपवाद को बढ़ाने जा रहा है।

इसके अलावा, मॉड्यूल त्वरित नहीं किया जा सकता है, लेकिन वे भी विरासत में नहीं मिल सकता है। नई विधि को निजी बनाने से भी काम नहीं चलेगा। कोई संकेत?


1
मॉड्यूल में मिलाया जा सकता है, लेकिन मुझे लगा कि आपको किसी और कारण से शास्त्रीय विरासत की आवश्यकता है?
Zach

6
ऐसा नहीं है कि मुझे एक अमूर्त वर्ग को लागू करने की आवश्यकता है। मैं सोच रहा था कि यह कैसे करना है, अगर सभी को इसे करने की आवश्यकता है। एक प्रोग्रामिंग समस्या। बस।
चिरंतन

127
raise "Doh! You are trying to write Java in Ruby"
एंड्रयू ग्रिम

जवाबों:


61

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

module Abstract
  def abstract_methods(*args)
    args.each do |name|
      class_eval(<<-END, __FILE__, __LINE__)
        def #{name}(*args)
          raise NotImplementedError.new("You must implement #{name}.")
        end
      END
      # important that this END is capitalized, since it marks the end of <<-END
    end
  end
end

require 'rubygems'
require 'rspec'

describe "abstract methods" do
  before(:each) do
    @klass = Class.new do
      extend Abstract

      abstract_methods :foo, :bar
    end
  end

  it "raises NoMethodError" do
    proc {
      @klass.new.foo
    }.should raise_error(NoMethodError)
  end

  it "can be overridden" do
    subclass = Class.new(@klass) do
      def foo
        :overridden
      end
    end

    subclass.new.foo.should == :overridden
  end
end

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


यह वास्तव में एक इंटरफ़ेस की तरह है लेकिन मुझे इसका विचार है। धन्यवाद।
चिरंतन

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

6
आप मेटा-प्रोग्रामिंग के साथ एक पंक्ति विधि की जगह ले रहे हैं, अब एक मिश्रण को शामिल करने और एक विधि को कॉल करने की आवश्यकता है। मुझे नहीं लगता कि यह अधिक घोषणात्मक है।
पास्कल

1
मैंने इस उत्तर को संपादित किया कुछ कोड को ठीक किया और इसे अपडेट किया, इसलिए यह अब चलता है। इसके अलावा कोड को थोड़ा सरल करें, इसमें शामिल होने के बजाय विस्तार का उपयोग करें: yehudakatz.com/2009/11/12/better-ruby-idioms
Magne

1
@ManishShrivastava: कृपया अब इस कोड को देखें, अंत में यहाँ अंत का उपयोग करने के महत्व के बारे में टिप्पणी के लिए
Magne

113

बस यहाँ देर में झंकार करने के लिए, मुझे लगता है कि किसी को अमूर्त वर्ग को तत्काल रोक देने का कोई कारण नहीं है, खासकर क्योंकि वे मक्खी पर इसके तरीकों को जोड़ सकते हैं

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

def get_db_name
   raise 'this method should be overriden and return the db name'
end

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

आपके प्रश्न में, आप मूल रूप abstractसे जावा से कीवर्ड को फिर से बनाने की कोशिश कर रहे हैं , जो कि रूबी में जावा करने के लिए एक कोड-गंध है।


3
@ क्रिस्टोफर पेरी: किसी भी कारण से?
SasQ

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

2
@ SasQ, इसे बनाने के लिए आपको मूल वर्ग के कार्यान्वयन विवरण को जानने की आवश्यकता नहीं है, आपको केवल इसके 'एपीआई' को जानना होगा। हालांकि, यदि आप विरासत में लेते हैं तो आप माता-पिता के कार्यान्वयन पर निर्भर होते हैं। क्या कार्यान्वयन परिवर्तन से आपका कोड अप्रत्याशित तरीके से टूट सकता है। यहां
क्रिस्टोफर पेरी

16
क्षमा करें, लेकिन "वंशानुक्रम पर अनुकूल रचना" "हमेशा उपयोगकर्ता संरचना" नहीं कहती है। हालांकि सामान्य वंशानुक्रम से बचा जाना चाहिए, वहाँ कुछ मामलों का उपयोग किया जाता है जब वे सिर्फ बेहतर फिट होते हैं। आँख बंद करके किताब का पालन न करें।
नाऊकेर

1
@ नोवेकर बहुत महत्वपूर्ण बिंदु। इसलिए अक्सर हम "हम इस मामले में व्यावहारिक दृष्टिकोण" के बारे में सोचने के बजाय, जो हम पढ़ते हैं या सुनते हैं, उसके द्वारा अंधा हो जाते हैं। यह शायद ही कभी पूरी तरह से काला या सफेद होता है।
लंडबर्ग

44

इसे इस्तेमाल करे:

class A
  def initialize
    raise 'Doh! You are trying to instantiate an abstract class!'
  end
end

class B < A
  def initialize
  end
end

38
यदि आप #initializeबी के सुपर का उपयोग करने में सक्षम होना चाहते हैं , तो आप वास्तव में ए # इनिशियलाइज़ में जो कुछ भी उठा सकते हैं if self.class == A
2:12 बजे mk12

17
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

7
इसके अतिरिक्त, कोई भी निर्माणकर्ता विधि को सभी उपवर्गों में स्वचालित रूप से दृश्यमान बनाने के लिए मूल वर्ग के विरासत में प्राप्त हुक का उपयोग कर सकता है: A.inherited (उपवर्ग) को परिभाषित करें; subclass.instance_eval {public_class_method: new}; अंत
t6d

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

16

रेल दुनिया में किसी के लिए, एक अमूर्त वर्ग के रूप में एक ActiveRecord मॉडल को लागू करना मॉडल फ़ाइल में इस घोषणा के साथ किया जाता है:

self.abstract_class = true

12

मेरा 2 I: मैं एक साधारण, हल्के DSL मिक्सिन का विकल्प चुनता हूं:

module Abstract
  extend ActiveSupport::Concern

  included do

    # Interface for declaratively indicating that one or more methods are to be
    # treated as abstract methods, only to be implemented in child classes.
    #
    # Arguments:
    # - methods (Symbol or Array) list of method names to be treated as
    #   abstract base methods
    #
    def self.abstract_methods(*methods)
      methods.each do |method_name|

        define_method method_name do
          raise NotImplementedError, 'This is an abstract base method. Implement in your subclass.'
        end

      end
    end

  end

end

# Usage:
class AbstractBaseWidget
  include Abstract
  abstract_methods :widgetify
end

class SpecialWidget < AbstractBaseWidget
end

SpecialWidget.new.widgetify # <= raises NotImplementedError

और, ज़ाहिर है, इस मामले में आधार वर्ग को शुरू करने के लिए एक और त्रुटि जोड़ना तुच्छ होगा।


1
संपादित करें: अच्छे उपाय के लिए, चूंकि यह दृष्टिकोण डिफाइन_मिथोड का उपयोग करता है, इसलिए कोई यह सुनिश्चित करना चाहता है कि बैकट्रेस बरकरार रहे, उदाहरण के लिए: err = NotImplementedError.new(message); err.set_backtrace caller()YMMV
एंथोनी नवरे

मुझे यह तरीका काफी सुरुचिपूर्ण लग रहा है। इस सवाल में आपके योगदान के लिए धन्यवाद।
wes.hysell

12

रूबी प्रोग्रामिंग के पिछले 6 1/2 वर्षों में, मुझे एक बार एक सार वर्ग की आवश्यकता नहीं है ।

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

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

अद्यतन २०१ ९: मुझे १६ of वर्षों के उपयोग में रूबी में अमूर्त कक्षाओं की आवश्यकता नहीं है। मेरी प्रतिक्रिया पर टिप्पणी करने वाले सभी लोग कह रहे हैं कि वास्तव में रूबी सीख रही है और मॉड्यूल जैसे उपयुक्त उपकरण का उपयोग करके सीख रही है (जो आपको सामान्य कार्यान्वयन भी देते हैं)। जिन टीमों पर मैंने काम किया है, उनमें ऐसे लोग हैं जिन्होंने आधार कार्यान्वयन करने वाली कक्षाएं बनाई हैं जो असफल होती हैं (एक अमूर्त वर्ग की तरह), लेकिन ये ज्यादातर कोडिंग की बर्बादी होती हैं क्योंकि NoMethodErrorउत्पादन में एक ही परिणाम का सटीक परिणाम होगा AbstractClassError


24
आपको जावा में / अमूर्त वर्ग की आवश्यकता नहीं है। यह दस्तावेज़ का एक तरीका है कि यह एक आधार वर्ग है और इसे आपके वर्ग का विस्तार करने वाले लोगों के लिए त्वरित नहीं किया जाना चाहिए।
fijiaaron

3
IMO, कुछ भाषाओं को ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग की आपकी धारणाओं को सीमित करना चाहिए। जब तक किसी प्रदर्शन से संबंधित कारण (या कुछ अधिक सम्मोहक) नहीं है, तब तक किसी दी गई स्थिति में क्या उपयुक्त है, यह भाषा पर निर्भर नहीं होना चाहिए।
दकिंगॉफ्टथ

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

1
(contd।) बेशक कुछ सामान्य तरीकों और गुणों के साथ उनमें से कई कक्षाएं बना सकते हैं, लेकिन संकलक / दुभाषिया को तब पता नहीं चलेगा कि ये कक्षाएं किसी भी तरह से संबंधित हैं। इन वर्गों में से प्रत्येक में विधियों और गुणों के नाम समान हो सकते हैं, लेकिन इसका मतलब यह नहीं है कि वे समान कार्यक्षमता का प्रतिनिधित्व करते हैं (नाम पत्राचार सिर्फ आकस्मिक हो सकता है)। इस संबंध के बारे में संकलक को बताने का एकमात्र तरीका आधार वर्ग का उपयोग करना है, लेकिन यह हमेशा इस आधार वर्ग के उदाहरणों के अस्तित्व के लिए समझ में नहीं आता है।
14


4

व्यक्तिगत रूप से मैं अमूर्त वर्गों के तरीकों में NotImplementedError बढ़ाता हूं। लेकिन आप बताए गए कारणों से इसे 'नई' विधि से छोड़ना चाह सकते हैं।


लेकिन फिर इसे तत्काल होने से कैसे रोका जाए?
चिरंतन

व्यक्तिगत रूप से मैं सिर्फ रूबी के साथ शुरू कर रहा हूं, लेकिन पायथन में, घोषित __init ___ () विधियों के साथ उपवर्ग स्वचालित रूप से अपने सुपरक्लासेस के __init __ () तरीकों को नहीं कहते हैं। मुझे उम्मीद है कि रूबी में भी इसी तरह की अवधारणा होगी, लेकिन जैसा मैंने कहा कि मैं अभी शुरू कर रहा हूं।
Zack

initializeजब तक स्पष्ट रूप से सुपर के साथ नहीं बुलाया जाता है माणिक माता-पिता के तरीकों में स्वचालित रूप से कॉल नहीं किया जाता है।
15:12 बजे mk12

4

यदि आप अपने A.new पद्धति में, एक अज्ञात श्रेणी के साथ जाना चाहते हैं, तो त्रुटि को फेंकने से पहले स्वयं == देखें कि क्या है।

लेकिन वास्तव में, एक मॉड्यूल अधिक लगता है जैसे आप यहां क्या चाहते हैं - उदाहरण के लिए, Enumerable एक ऐसी चीज है जो अन्य भाषाओं में एक सार वर्ग हो सकती है। आप तकनीकी रूप से उन्हें उप-वर्ग नहीं कर सकते हैं, लेकिन कॉलिंग include SomeModuleसमान लक्ष्य को प्राप्त करता है। क्या कोई कारण है कि यह आपके लिए काम नहीं करेगा?


4

आप एक सार वर्ग के साथ क्या करने की कोशिश कर रहे हैं? माणिक में इसे करने का एक बेहतर तरीका है, लेकिन आपने कोई विवरण नहीं दिया।

मेरा सूचक यह है; का प्रयोग कर एक mixin विरासत नहीं।


वास्तव में। एक मॉड्यूल का मिश्रण एक AbstractClass का उपयोग करने के बराबर होगा: wiki.c2.com/?AbstractClass PS: चलो उन्हें मॉड्यूल कहते हैं और मिश्रण नहीं, क्योंकि मॉड्यूल वे क्या हैं और उन्हें मिश्रण में है कि आप उनके साथ क्या करते हैं।
मैग्ने

3

एक और जवाब:

module Abstract
  def self.append_features(klass)
    # access an object's copy of its class's methods & such
    metaclass = lambda { |obj| class << obj; self ; end }

    metaclass[klass].instance_eval do
      old_new = instance_method(:new)
      undef_method :new

      define_method(:inherited) do |subklass|
        metaclass[subklass].instance_eval do
          define_method(:new, old_new)
        end
      end
    end
  end
end

यह सामान्य #method_missing पर निर्भर करता है ताकि अनिमित विधियों की रिपोर्ट की जा सके, लेकिन अमूर्त वर्गों को कार्यान्वित होने से रोका जाता है (भले ही उनके पास एक प्रारंभिक विधि हो)

class A
  include Abstract
end
class B < A
end

B.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>

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


3

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

puts 'test inheritance'
module Abstract
  def new
    throw 'abstract!'
  end
  def inherited(child)
    @abstract = true
    puts 'inherited'
    non_abstract_parent = self.superclass;
    while non_abstract_parent.instance_eval {@abstract}
      non_abstract_parent = non_abstract_parent.superclass
    end
    puts "Non abstract superclass is #{non_abstract_parent}"
    (class << child;self;end).instance_eval do
      define_method :new, non_abstract_parent.method('new')
      # # Or this can be done in this style:
      # define_method :new do |*args,&block|
        # non_abstract_parent.method('new').unbind.bind(self).call(*args,&block)
      # end
    end
  end
end

class AbstractParent
  extend Abstract
  def initialize
    puts 'parent initializer'
  end
end

class Child < AbstractParent
  def initialize
    puts 'child initializer'
    super
  end
end

# AbstractParent.new
puts Child.new

class AbstractChild < AbstractParent
  extend Abstract
end

class Child2 < AbstractChild

end
puts Child2.new

3

वहाँ भी यह छोटा है abstract_type मणि, अमूर्त वर्गों और मॉड्यूल की घोषणा करने के लिए एक unobstrusive तरीके से अनुमति देता है।

उदाहरण ( README.md फ़ाइल से):

class Foo
  include AbstractType

  # Declare abstract instance method
  abstract_method :bar

  # Declare abstract singleton method
  abstract_singleton_method :baz
end

Foo.new  # raises NotImplementedError: Foo is an abstract type
Foo.baz  # raises NotImplementedError: Foo.baz is not implemented

# Subclassing to allow instantiation
class Baz < Foo; end

object = Baz.new
object.bar  # raises NotImplementedError: Baz#bar is not implemented

1

आपके दृष्टिकोण में कुछ भी गलत नहीं है। आरंभ में त्रुटि उठाएँ ठीक लगता है, जब तक कि आपके सभी उपवर्ग पाठ्यक्रम के आरंभ को ओवरराइड नहीं करते। लेकिन आप उस तरह self.new को परिभाषित नहीं करना चाहते हैं। यहाँ मैं क्या करूँगा।

class A
  class AbstractClassInstiationError < RuntimeError; end
  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
  end
end

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

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


मैं ऐसा नहीं करना चाहता। बच्चे तब "सुपर" नहीं कह सकते।
ऑस्टिन ज़िगलर


0

हालांकि यह रूबी की तरह महसूस नहीं करता है, आप ऐसा कर सकते हैं:

class A
  def initialize
    raise 'abstract class' if self.instance_of?(A)

    puts 'initialized'
  end
end

class B < A
end

परिणाम:

>> A.new
  (rib):2:in `main'
  (rib):2:in `new'
  (rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.