Come implementare una class astratta in ruby?

So che non esiste un concetto di class astratta nel ruby. Ma se ha bisogno di essere implementato, come farlo? Ho provato qualcosa come …

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

Ma quando provo a istanziare B, internamente chiamerò A.new che farà sorgere l’eccezione.

Inoltre, i moduli non possono essere istanziati ma non possono essere ereditati. rendere il nuovo metodo privato non funzionerà. Qualche indicazione?

Non mi piace usare lezioni astratte in Ruby (c’è quasi sempre un modo migliore). Se pensi davvero che sia la tecnica migliore per la situazione, puoi usare il seguente frammento per essere più dichiarativo su quali siano i metodi astratti:

 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 

Fondamentalmente, si chiama abstract_methods con l'elenco di metodi che sono astratti e quando vengono richiamati da un'istanza della class astratta, verrà sollevata un'eccezione NotImplementedError .

Giusto per arrivare qui tardi, penso che non ci sia motivo di impedire a qualcuno di istanziare la class astratta, specialmente perché possono aggiungere metodi al volo .

I linguaggi di duck-typing, come Ruby, usano la presenza / assenza o il comportamento dei metodi in fase di esecuzione per determinare se devono essere chiamati o meno. Quindi la tua domanda, come si applica a un metodo astratto, ha senso

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

e dovrebbe essere circa la fine della storia. L’unica ragione per usare classi astratte in Java è insistere sul fatto che alcuni metodi vengano “riempiti” mentre altri hanno il loro comportamento nella class astratta. In un linguaggio di tipizzazione, il focus è sui metodi, non su classi / tipi, quindi dovresti spostare le tue preoccupazioni a quel livello.

Nella tua domanda, stai praticamente cercando di ricreare la parola chiave abstract da Java, che è un odore di codice per fare Java in Ruby.

Prova questo:

 class A def initialize raise 'Doh! You are trying to instantiate an abstract class!' end end class B < A def initialize end end 
 class A private_class_method :new end class B < A public_class_method :new end 

My 2 ¢: opto per un mix di DSL semplice e leggero:

 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 classs. # # 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 

E, naturalmente, aggiungere un altro errore per inizializzare la class base sarebbe banale in questo caso.

Negli ultimi 6 anni e mezzo di programmazione di Ruby, non ho avuto bisogno di una lezione astratta una volta.

Se stai pensando che hai bisogno di una lezione astratta, stai pensando troppo in un linguaggio che fornisce / richiede loro, non in Ruby in quanto tale.

Come altri hanno suggerito, un mixin è più appropriato per cose che dovrebbero essere interfacce (come le definisce Java), e ripensare il tuo design è più appropriato per cose che “hanno bisogno” di classi astratte da altri linguaggi come il C ++.

per chiunque nel mondo delle rotaie, l’implementazione di un modello ActiveRecord come una class astratta viene eseguita con questa dichiarazione nel file del modello:

 self.abstract_class = true 

Puoi provare 3 rubini:
interfaccia
astratto
semplice astratto

Se vuoi andare con una class non affidabile, nel tuo metodo A.new, controlla se auto == A prima di lanciare l’errore.

Ma in realtà, un modulo sembra più simile a quello che vuoi qui – per esempio, Enumerable è il genere di cosa che potrebbe essere una class astratta in altre lingue. Tecnicamente non puoi creare una sottoclass, ma chiamare include SomeModule raggiunge quasi lo stesso objective. C’è qualche ragione per cui questo non funzionerà per te?

Che scopo stai cercando di servire con una class astratta? C’è probabilmente un modo migliore per farlo in ruby, ma non hai dato alcun dettaglio.

Il mio puntatore è questo; usa un mixin non ereditarietà.

C’è anche questo piccolo gioiello abstract_type , che consente di dichiarare classi e moduli astratti in modo non ostruente.

Esempio (dal file 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 

Personalmente rilancio NotImplementedError in metodi di classi astratte. Ma potresti voler lasciarlo fuori dal “nuovo” metodo, per le ragioni che hai menzionato.

Un’altra risposta:

 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 

Questo si basa sul normale #method_missing per riportare metodi non implementati, ma impedisce l'implementazione di classi astratte (anche se hanno un metodo di inizializzazione)

 class A include Abstract end class B < A end B.new #=> # A.new # raises # 

Come hanno detto gli altri poster, probabilmente dovresti usare un mixin, piuttosto che una class astratta.

L’ho fatto in questo modo, quindi ridefinisce una nuova class su child per trovare una nuova class non astratta. Non vedo ancora alcuna pratica nell’uso di classi astratte in ruby.

 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 

Niente di sbagliato nel tuo approccio. Sollevare un errore durante l’inizializzazione sembra corretto, purché tutte le sottoclassi sovrascrivano l’inizializzazione, naturalmente. Ma tu non vuoi definire se stesso. Nuovo in questo modo. Ecco cosa farei.

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

Un altro approccio sarebbe mettere tutta quella funzionalità in un modulo, che come hai detto non può mai essere istanziato. Quindi includi il modulo nelle tue classi piuttosto che ereditare da un'altra class. Tuttavia, questo romperebbe cose come super.

Quindi dipende da come lo vuoi strutturare. Sebbene i moduli sembrino una soluzione più pulita per risolvere il problema di "Come scrivere alcune cose che sono degnate per le altre classi da utilizzare"