如何在ruby中实现抽象类?

时间:2009-02-04 17:35:43

标签: ruby abstract-class

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

此外,模块无法实例化,但也无法继承。使新方法私有也行不通。有什么指针吗?

17 个答案:

答案 0 :(得分:109)

在这里晚些时候,我认为没有理由阻止某人实例化抽象类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的代码味道。

答案 1 :(得分:56)

我不喜欢在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异常。

答案 2 :(得分:40)

试试这个:

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

class B < A
  def initialize
  end
end

答案 3 :(得分:14)

class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

答案 4 :(得分:12)

我的2¢:我​​选择了简单轻巧的DSL mixin:

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

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

答案 5 :(得分:10)

对于rails世界中的任何人来说,在模型文件中使用此声明实现ActiveRecord模型作为抽象类:

self.abstract_class = true

答案 6 :(得分:9)

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

如果您认为自己需要一个抽象类,那么您在提供/需要它的语言中会考虑太多,而不是在Ruby中。

正如其他人所建议的那样,mixin更适合那些应该是接口的东西(正如Java定义的那样),重新思考你的设计更适合那些需要来自其他语言(如C ++)的抽象类的东西。 / p>

2019年更新:在使用16年半的时间里,我不需要Ruby中的抽象类。通过实际学习Ruby并使用适当的工具(如模块(甚至可以为您提供常见实现)),可以解决所有人对我的回复所做的评论。我管理的团队中有些人创建了基本实现失败的类(如抽象类),但这些人大多浪费编码,因为NoMethodError会产生与{{1}完全相同的结果在生产中。

答案 7 :(得分:6)

你可以尝试3个rubygems:
interface
abstract
simple abstract

答案 8 :(得分:4)

如果你想使用一个不可实例化的类,在你的A.new方法中,在抛出错误之前检查self == A.

但实际上,一个模块看起来更像你想要的 - 例如,Enumerable可能是其他语言中的抽象类。从技术上讲,你不能将它们子类化,但调用include SomeModule可以实现大致相同的目标。有什么理由不适合你?

答案 9 :(得分:4)

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

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

答案 10 :(得分: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来报告未实现的方法, 但是要保持抽象类的实现(即使它们有初始化方法)

class A
  include Abstract
end
class B < A
end

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

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

答案 11 :(得分:3)

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

答案 12 :(得分: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

答案 13 :(得分:3)

还有这个小的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

答案 14 :(得分:1)

你的方法没有错。初始化中的错误似乎很好,只要所有子类都覆盖初始化当然。但是你不想像那样定义self.new。这就是我要做的事。

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

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

所以这取决于你想要如何构建它。虽然模块似乎是一个更清晰的解决方案,可以解决“我如何编写一些其他类使用的东西”的问题

答案 15 :(得分:1)

答案 16 :(得分:1)

尽管这听起来不像Ruby,但是您可以这样做:

class A
  def initialize
    raise 'abstract class' if self.instance_of?(A)

    puts 'initialized'
  end
end

class B < A
end

结果:

>> A.new
  (rib):2:in `main'
  (rib):2:in `new'
  (rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>