2009-02-04 28 views
93

Sé que no existe el concepto de clase abstracta en ruby. Pero si es necesario implementarlo, ¿cómo hacerlo? He intentado algo así como ...¿Cómo implementar una clase abstracta en ruby?

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

class B < A 
    ... 
    ... 
end 

Pero cuando intento crear una instancia de B, que es internamente iba a llamar A.new que se va a levantar la excepción.

Además, los módulos no se pueden crear instancias pero tampoco se pueden heredar. hacer que el nuevo método sea privado tampoco funcionará. ¿Alguna sugerencia?

+1

Los módulos se pueden mezclar, ¿pero supongo que necesitas herencia clásica por algún otro motivo? – Zach

+6

No es que necesite una clase abstracta. Me preguntaba cómo hacerlo, si es que uno tiene que hacerlo. Un problema de programación. Eso es. – Chirantan

+103

'raise" Doh! Estás tratando de escribir Java en Ruby "'. –

Respuesta

53

No me gusta usar clases abstractas en Ruby (casi siempre hay una mejor manera). Si realmente cree que es la mejor técnica para la situación, sin embargo, se puede utilizar el siguiente fragmento de ser más declarativa sobre qué métodos son abstractos:

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 

Básicamente, que acaba de llamar abstract_methods con la lista de métodos que son abstractos y cuando reciben un llamado de una instancia de la clase abstracta, se generará una excepción NotImplementedError.

+0

Esto es más como una interfaz en realidad pero entiendo la idea. Gracias. – Chirantan

+4

Esto no suena como un caso de uso válido de 'NotImplementedError' que esencialmente significa" dependiente de la plataforma, no disponible en el suyo ". [Ver documentos] (http://ruby-doc.org/core-1.9.3/NotImplementedError.html). – skalee

+3

Está reemplazando un método de una línea con metaprogramación, ahora necesita incluir un mixin y llamar a un método. No creo que esto sea más declarativo en absoluto. –

3

Personalmente elevo NotImplementedError en métodos de clases abstractas. Pero es posible que desee dejarlo fuera del 'nuevo' método, por las razones que ha mencionado.

+0

Pero, ¿cómo evitar que se cree una instancia? – Chirantan

+0

Personalmente, recién estoy comenzando con Ruby, pero en Python, las subclases con métodos __init ___() declarados no llaman automáticamente a los métodos __init __() de las superclases. Espero que haya un concepto similar en Ruby, pero como dije, estoy empezando. – Zack

+0

En los métodos 'initialize' de padres ruby ​​no se invocan automáticamente a menos que se los llame explícitamente con super. – mk12

1

Nada de malo con su enfoque. Generar un error al inicializar parece estar bien, siempre que todas las subclases anuladas se inicialicen, por supuesto. Pero no quieres definir self.new así. Esto es lo que haría.

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

Otro enfoque sería poner toda esa funcionalidad en un módulo, que como usted ha mencionado nunca puede ser instiated. Luego incluya el módulo en sus clases en lugar de heredar de otra clase. Sin embargo, esto rompería cosas como súper.

Por lo tanto, depende de cómo desee estructurarlo. Aunque los módulos parecen como una solución más limpia para solucionar el problema de "¿Cómo escribo algunas cosas que se dignó para otras clases de usar"

+0

No me gustaría hacer eso tampoco. Los niños no pueden llamar "súper", entonces. –

36

Prueba esto:

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

class B < A 
    def initialize 
    end 
end 
+1

esto está bien +1 –

+34

Si quieres poder usar super en '# initialize' de B, puedes simplemente subir lo que sea en A # initialize' if self.class == A'. – mk12

4

Si quieres ir con un crearse instancias clase, en su método A.new, compruebe si self == A antes de lanzar el error.

Pero en realidad, un módulo se parece más a lo que quiere aquí, por ejemplo, Enumerable es el tipo de cosa que podría ser una clase abstracta en otros idiomas. Técnicamente no puedes subclasificarlos, pero llamar al include SomeModule logra aproximadamente el mismo objetivo. ¿Hay alguna razón por la que esto no funcionará para ti?

3

¿Qué propósito intentas ofrecer con una clase abstracta? Probablemente haya una mejor manera de hacerlo en ruby, pero no proporcionó ningún detalle.

Mi puntero es esto; use mixin no herencia.

+0

De hecho. Mezclar un Módulo sería equivalente a usar una AbstractClass: http://wiki.c2.com/?AbstractClass PS: Llamémosles módulos y no mixins, ya que los módulos son lo que son y mezclarlos es lo que haces con ellos. – Magne

3

Otra respuesta:

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 

Esto se basa en la #method_missing normal que informan métodos no implementados, pero mantiene las clases abstractas que se implemente (incluso si tienen un método initialize)

class A 
    include Abstract 
end 
class B < A 
end 

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

Como han dicho los otros carteles, probablemente deberías estar usando mixin, en lugar de una clase abstracta.

10

En los últimos 6 1/2 años de programación Ruby, no tengo necesito una clase abstracta una vez.

Si piensas que necesitas una clase abstracta, estás pensando demasiado en un lenguaje que los proporcione/requiera, no en Ruby como tal.

Como otros han sugerido, un mixin es más apropiado para cosas que se supone que son interfaces (como Java las define) y replantear su diseño es más apropiado para cosas que "necesitan" clases abstractas de otros lenguajes como C++.

+18

No necesita/necesita/una clase abstracta en Java tampoco. Es una forma de documentar que es una clase base y no debe crearse una instancia para las personas que extienden su clase. – fijiaaron

+2

IMO, algunos idiomas deben restringir sus nociones de programación orientada a objetos. Lo que es apropiado en una situación determinada no debe depender del idioma, a menos que exista un motivo relacionado con el rendimiento (o algo más convincente). – thekingoftruth

+10

@fijiaaron: Si lo crees, entonces definitivamente no entiendes qué son las clases base abstractas. No son mucho para "documentar" que una clase no debe ser instanciada (es más bien un efecto secundario de que sea abstracto). Se trata más de establecer una interfaz común para un grupo de clases derivadas, lo que garantiza que se implementará (si no, la clase derivada también se mantendrá abstracta). Su objetivo es apoyar el Principio de Sustitución de Liskov para las clases para las cuales la creación de instancias no tiene mucho sentido. – SasQ

11
class A 
    private_class_method :new 
end 

class B < A 
    public_class_method :new 
end 
+5

Además, uno podría usar el enganche heredado de la clase padre para hacer que el método constructor sea automáticamente visible en todas las subclases: def A.inherited (subclase); subclass.instance_eval {public_class_method: new}; end – t6d

+1

Very nice t6d. Como comentario, solo asegúrate de que esté documentado, ya que es un comportamiento sorprendente (viola la menor sorpresa). – bluehavana

102

Justo a sonar a finales de aquí, creo que no hay ninguna razón para detener a alguien de crear instancias de la clase abstracta, especially because they can add methods to it on the fly.

Los lenguajes de pato, como Ruby, usan la presencia/ausencia o el comportamiento de los métodos en tiempo de ejecución para determinar si deben llamarse o no. Por lo tanto su pregunta, ya que se aplica a un resumen método, tiene sentido

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

y que debe ser sobre el final de la historia. La única razón para usar clases abstractas en Java es insistir en que ciertos métodos se "completan" mientras que otros tienen su comportamiento en la clase abstracta. En un lenguaje de pato, el foco está en los métodos, no en las clases/tipos, por lo que debe mover sus preocupaciones a ese nivel.

En su pregunta, básicamente está tratando de recrear la palabra clave abstract de Java, que es un olor de código para hacer Java en Ruby.

+2

Las clases abstractas son un olor a código, incluso en Java. –

+3

@Christopher Perry: ¿Alguna razón por qué? – SasQ

+0

@SasQ porque introduce un acoplamiento ajustado en su código. Crea una dependencia entre el padre y el hermano. Un mejor enfoque, que logra el mismo efecto sin acoplamiento es usar composición en lugar de herencia. –

3

Lo hice de esta manera, por lo que redefine las novedades en la clase infantil para encontrar una nueva en la clase no abstracta. Todavía no veo ninguna práctica en el uso de clases abstractas en 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 
11

Mi 2 ¢: opto por un simple mixin, DSL ligera:

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 

Y, por supuesto, agregar otro error para inicializar la clase base sería trivial en este caso.

+1

EDITAR: para una buena medida, ya que este enfoque utiliza define_method, uno puede querer asegurarse de que backtrace permanece intacto, por ejemplo: 'err = NotImplementedError.new (mensaje); err.set_backtrace caller() 'YMMV –

+0

Encuentro que este enfoque es bastante elegante. Gracias por tu contribución a esta pregunta. –

4

También existe esta pequeña gema abstract_type, que permite declarar clases abstractas y módulos de una manera discreta.

Ejemplo (del archivo 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 
5

para cualquier persona en el mundo carriles, la implementación de un modelo de ActiveRecord como una clase abstracta se hace con esta declaración en el archivo de modelo:

self.abstract_class = true 
Cuestiones relacionadas