131

我知道 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会引发异常的调用。

此外,模块不能被实例化,但它们也不能被继承。将新方法设为私有也行不通。

有没有人有任何指示?

4

17 回答 17

123

只是在这里插话,我认为没有理由阻止某人实例化抽象类,特别是因为他们可以动态添加方法

像 Ruby 这样的鸭子类型语言在运行时使用方法的存在/不存在或行为来确定是否应该调用它们。因此,您的问题适用于抽象方法,是有道理的

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

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

在您的问题中,您基本上是在尝试abstract从 Java 重新创建关键字,这是在 Ruby 中执行 Java 的代码气味。

于 2010-03-23T19:05:31.467 回答
61

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

于 2009-02-04T17:48:27.410 回答
46

试试这个:

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

class B < A
  def initialize
  end
end
于 2009-02-04T18:06:50.073 回答
20

对于 Rails 世界中的任何人,将 ActiveRecord 模型实现为抽象类是通过模型文件中的以下声明完成的:

self.abstract_class = true
于 2015-05-26T18:19:36.867 回答
19
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
于 2009-02-06T02:11:32.027 回答
14

在过去 6 1/2 年的 Ruby 编程中,我一次不需要抽象类。

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

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

2022 年更新:在 20 年的使用中,我不需要 Ruby 中的抽象类。所有对我的回复发表评论的人所说的一切都是通过实际学习 Ruby 和使用适当的工具来解决的,比如模块(甚至为您提供通用实现)。在我管理的团队中,有些人创建了具有失败的基本实现的类(如抽象类),但这些主要是浪费编码,因为NoMethodError会产生与生产中完全相同的结果AbstractClassError

于 2009-02-04T20:27:44.703 回答
13

我的 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

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

于 2012-05-10T06:15:35.203 回答
7

您可以尝试 3 个 ruby​​gems:
接口
抽象
简单抽象

于 2011-08-12T10:32:22.280 回答
6

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

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

于 2009-02-04T20:19:45.610 回答
4

就个人而言,我提出NotImplementedError了抽象类的方法。new但是由于您提到的原因,您可能希望将其排除在方法之外。

于 2009-02-04T17:39:40.363 回答
4

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

但实际上,一个模块看起来更像你想要的。例如,Enumerable 是一种在其他语言中可能是抽象类的东西。从技术上讲,您不能将它们子类化,但调用include SomeModule可以实现大致相同的目标。有什么原因这对你不起作用吗?

于 2009-02-04T19:08:03.163 回答
4

另一个答案:

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,而不是抽象类。

于 2009-02-04T20:23:05.727 回答
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
于 2010-10-29T09:30:32.217 回答
3

还有这个小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
于 2014-09-08T05:15:12.670 回答
1

你的方法没有错。在初始化程序中引发错误似乎很好,只要您的所有子类initialize当然都覆盖。但是你不想这样定义self.new。这是我要做的:

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

另一种方法是将所有功能放在一个模块中,正如您所提到的,它永远无法实例化。然后,将模块包含在您的类中,而不是从另一个类继承。但是,这会破坏super.

这取决于您希望如何构建它,尽管模块似乎是解决“我如何编写一些设计供其他类使用的东西”问题的更清洁的解决方案

于 2009-02-04T17:48:33.713 回答
1

2 行宝石:https ://rubygems.org/gems/abstract

于 2011-07-05T12:01:11.097 回答
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>
>>
于 2020-06-05T04:25:07.490 回答