如何在ruby中实现一个抽象类?
我知道ruby中没有抽象类的概念。 但是,如果一切都需要执行,怎么去做呢? 我尝试了一些…
class A def self.new raise 'Doh! You are trying to write Java in Ruby!' end end class B < A ... ... end
但是当我尝试实例化B时,它将在内部打电话给A.new
,这将引发exception。
而且,模块不能被实例化,但是它们也不能被inheritance。 使新的方法私人也将无法正常工作。 任何指针?
我不喜欢在Ruby中使用抽象类(几乎总是有一个更好的方法)。 如果你真的认为这是最好的技术,那么你可以使用下面的代码片断来说明哪些方法是抽象的:
module Abstract def self.included(base) base.extend(ClassMethods) end module ClassMethods 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 end end end end require 'rubygems' require 'spec' describe "abstract methods" do before(:each) do @klass = Class.new do include Abstract abstract_methods :foo, :bar end end it "raises NotImplementedError" do proc { @klass.new.foo }.should raise_error(NotImplementedError) 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
与abstract_methods
方法列表一起调用,并在被抽象类的实例调用时,就会引发NotImplementedError
exception。
只是为了在这里迟到,我认为没有理由阻止某人实例化抽象类, 特别是因为他们可以在其上添加方法 。
像Ruby这样的鸭式语言在运行时使用方法的存在/缺失或行为来确定它们是否应该被调用。 因此,你的问题,因为它适用于抽象的方法 ,是有道理的
def get_db_name raise 'this method should be overriden and return the db name' end
这应该是关于故事的结尾。 在Java中使用抽象类的唯一原因是坚持某些方法被“填充”,而另一些方法在抽象类中有行为。 用鸭子打字的语言,重点是方法,而不是class级/types,所以你应该把你的担心转移到那个水平。
在你的问题中,你基本上是从Java重新创buildabstract
关键字,这是在Ruby中做Java的代码。
尝试这个:
class A def initialize raise 'Doh! You are trying to instantiate an abstract class!' end end class B < A def initialize end end
我的2¢:我select一个简单,轻量级的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
当然,在这种情况下,添加另一个用于初始化基类的错误将是微不足道的。
class A private_class_method :new end class B < A public_class_method :new end
在编程Ruby的最后6年半中,我不需要一次抽象类。
如果你认为你需要一个抽象类,那么你用一种提供/需要它的语言来思考太多,而不是Ruby。
正如其他人所build议的,mixin更适合于被认为是接口的东西(就像Java定义的那样),重新考虑你的devise更适合那些需要从C ++等其他语言抽象类的东西。
对于Rails世界中的任何人来说,在模型文件中使用这个声明来实现ActiveRecord模型作为抽象类:
self.abstract_class = true
如果你想用一个无法实现的类,在你的A.new方法中,在抛出错误之前检查自己是否是==。
但实际上,一个模块看起来更像你想要的东西 – 例如,Enumerable就是其他语言中可能是抽象类的东西。 你在技术上不能include SomeModule
它们,但调用include SomeModule
实现了大致相同的目标。 有什么理由不适合你吗?
你可以试试3个rubygems:
接口
抽象
简单的抽象
还有这个小的abstract_type
gem,允许以一种不显眼的方式声明抽象的类和模块。
示例(来自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
我个人在抽象类的方法中提出NotImplementedError。 但是,由于你提到的原因,你可能想把它从“新”方法中排除出去。
另一个答案:
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>
就像其他海报所说的那样,你可能应该使用mixin,而不是抽象类。
我这样做,所以它重新定义了新的子类,以find一个新的非抽象类。 在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
你想用抽象类来服务什么目的? 在Ruby中可能有更好的方法,但是你没有提供任何细节。
我的指针是这个; 使用mixin而不是inheritance。
你的方法没有错。 在初始化时引发一个错误似乎很好,只要你所有的子类重写initialize当然。 但是你不想像这样定义self.new。 这是我会做的。
class A class AbstractClassInstiationError < RuntimeError; end def initialize raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..." end end
另一种方法是将所有这些function放在一个模块中,正如你所提到的,这个模块是永远不会被调用的。 然后将模块包含在您的类中,而不是从另一个类inheritance。 但是,这会打破超级的东西。
所以这取决于你想如何构build它。 虽然模块似乎是一个更清洁的解决scheme,可以解决“如何编写一些为其他类使用而devise的东西”
2行gem: https : //rubygems.org/gems/abstract