2009-02-04 95 views
93

我知道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这是会引发异常。

此外,模块不能被实例化,但它们也不能被继承。使新方法私人化也将无法正常工作。任何指针?

+1

模块可以混入,但我认为你需要经典继承的其他原因? – Zach 2009-02-04 17:41:44

+6

这并不是说我需要实现一个抽象类。我想知道如何去做,如果有需要的话。一个编程问题。而已。 – Chirantan 2009-02-04 17:46:27

+103

'raise'Doh!你正试图在Ruby中编写Java``。 – 2011-08-14 23:30:46

回答

53

我不喜欢在Ruby中使用抽象类(几乎总是有更好的方法)。如果你真的认为这是情况最好的技术,虽然,你可以用下面的代码片段更声明哪些方法是抽象:

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异常。

+0

这更像是一个界面,但我明白了。谢谢。 – Chirantan 2009-10-08 09:15:32

+4

这听起来并不是`NotImplementedError`的有效用例,它实质上意味着“依赖于平台,不适用于您”。 [见文档](http://ruby-doc.org/core-1.9.3/NotImplementedError.html)。 – skalee 2014-08-30 15:30:34

+3

您正在用元编程替换一行方法,现在需要包含一个mixin并调用一个方法。我不认为这是更具说明性的。 – 2014-12-07 13:47:41

3

我个人会在抽象类的方法中引发NotImplementedError。但出于你提到的原因,你可能希望将它从“新”方法中删除。

+0

但是如何阻止它被实例化? – Chirantan 2009-02-04 17:42:05

+0

就我个人而言,我刚刚开始使用Ruby,但在Python中,声明__init ___()方法的子类不会自动调用其超类的__init __()方法。我希望Ruby中会有类似的概念,但就像我说的我刚刚开始。 – Zack 2009-02-04 17:45:22

+0

在ruby父类的`initialize`方法中,除非用super显式调用,否则不会自动调用。 – mk12 2010-07-31 15:17:00

1

你的方法没有问题。在initialize中引发错误似乎很好,只要所有子类都重写initialize。但是你不想像这样定义self.new。这是我会做的。

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

另一种方法是将所有功能放在模块中,正如你所提到的,这是永远不会被调用的。然后将模块包含在您的类中,而不是从另一个类继承。但是,这会打破超级的东西。

所以这取决于你想如何构造它。虽然模块看起来像解决的问题更清洁的解决方案“我怎样写一些东西,是屈尊于其他类使用”

36

试试这个:如果你想要去的一个不可

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

class B < A 
    def initialize 
    end 
end 
4

类,在你的A.new方法中,在抛出错误之前检查self == A.

但实际上,模块看起来更像你想要的 - 例如,Enumerable就是可能是其他语言中抽象类的东西。你在技术上不能将它们继承,但呼叫include SomeModule实现了大致相同的目标。有没有什么理由不适合你?

3

你想用抽象类来服务什么目的?在ruby中可能有更好的方法,但是你没有提供任何细节。

我的指针是这样的;使用mixin不是继承。

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报告未实现的方法, 但正在实施保持抽象类(即使他们有一个initialize方法)

class A 
    include Abstract 
end 
class B < A 
end 

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

就像其他海报所说的那样,你可能应该使用mixin,而不是抽象类。

10

在Ruby编程的最后6年半,我没有需要一次抽象类。

如果你认为你需要一个抽象类,那么你提供/需要的语言太多了,而不是Ruby。如其他人所建议的,mixin更适合于被认为是接口的东西(正如Java定义它们),而重新考虑你的设计更适合那些需要从C++等其他语言抽象类的东西。

11
class A 
    private_class_method :new 
end 

class B < A 
    public_class_method :new 
end 
102

只是附和下旬这里,我认为没有理由从实例化抽象类,especially because they can add methods to it on the fly停止某人。

类似于Ruby的鸭式语言在运行时使用方法的存在/缺失或行为来确定它们是否应该被调用。所以你的问题,因为它适用于一个抽象方法,有道理

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

这应该是这个故事的结尾。在Java中使用抽象类的唯一原因是坚持某些方法被“填充”,而其他方法在抽象类中有行为。在鸭子打字的语言中,重点是方法,而不是类/类型,所以你应该将你的担忧转移到那个水平。

在你的问题中,你基本上试图从Java重新创建abstract关键字,这是在Ruby中执行Java的代码嗅觉。

3

我这样做,所以它重新定义了新的子类,以找到一个新的非抽象类。 我仍然没有看到在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

我的2¢:我​​选择一个简单,轻便的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 

而且,当然,在这种情况下,添加另一个用于初始化基类的错误将是微不足道的。

4

还有这个小巧的abstract_type宝石,允许以毫不突兀的方式声明抽象类和模块。

示例(从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

在轨世界上任何人,作为一个抽象类与此声明在模型文件进行实施ActiveRecord模型:

self.abstract_class = true