polimorfismo - que es una clase en ruby
¿Cómo implementar una clase abstracta en ruby? (16)
Sé que no existe un concepto de clase abstracta en ruby. Pero si es necesario implementarlo, ¿cómo hacerlo? Intenté algo como ...
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, internamente va a llamar a A.new
que va a generar 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?
¿Para qué intentas servir con una clase abstracta? Probablemente haya una mejor manera de hacerlo en ruby, pero no proporcionó ningún detalle.
Mi puntero es esto; usa un mixin no herencia.
En los últimos 6 1/2 años de programación de Ruby, no he necesitado una clase abstracta una vez.
Si estás pensando que necesitas una clase abstracta, estás pensando demasiado en un lenguaje que los proporciona / requiere, 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 repensar su diseño es más apropiado para cosas que "necesitan" clases abstractas de otros lenguajes como C ++.
Gema de 2 líneas: https://rubygems.org/gems/abstract
Lo hice de esta manera, por lo que redefine la clase nueva en el niño 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
Mi 2 ¢: opto por una mezcla DSL simple y liviana:
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.
No hay nada 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 mencionó nunca puede ser instigado. Luego incluya el módulo en sus clases en lugar de heredar de otra clase. Sin embargo, esto rompería cosas como súper.
Entonces depende de cómo quieras estructurarlo. Aunque los módulos parecen una solución más limpia para resolver el problema de "¿Cómo escribo algunas cosas que está diseñado para otras clases?"
No me gusta usar clases abstractas en Ruby (casi siempre hay una mejor manera). Sin embargo, si realmente crees que es la mejor técnica para la situación, puedes usar el siguiente fragmento para ser más declarativo 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, simplemente llama a abstract_methods
con la lista de métodos que son abstractos, y cuando una instancia de la clase abstracta los NotImplementedError
, se NotImplementedError
una excepción NotImplementedError
.
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 el #method_missing normal para informar métodos no implementados, pero evita que se implementen clases abstractas (incluso si tienen un método de inicialización)
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.
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.
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
Puedes probar 3 rubygems:
interface
abstract
simple resumen
Si desea ir con una clase que no se puede desinstalar, en su método A.new, verifique si self == A antes de arrojar 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 puede subclasificarlos, pero llamar include SomeModule
logra aproximadamente el mismo objetivo. ¿Hay alguna razón por la que esto no funcionará para ti?
Solo para sonar aquí tarde, creo que no hay razón para evitar que alguien cree una instancia de la clase abstracta, especialmente porque pueden agregar métodos sobre la marcha .
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, tal como se aplica a un método abstracto, tiene sentido
def get_db_name
raise ''this method should be overriden and return the db name''
end
y eso debería 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.
También existe esta pequeña gema de tipo 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
para cualquier persona en el mundo de los rieles, la implementación de un modelo de ActiveRecord como clase abstracta se hace con esta declaración en el archivo de modelo:
self.abstract_class = true
class A
private_class_method :new
end
class B < A
public_class_method :new
end