如何在 Ruby 中实现抽象类

发布于 2024-07-13 13:53:56 字数 329 浏览 3 评论 0原文

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

此外,模块不能被实例化,但也不能被继承。 将新方法设为私有也不起作用。

有人有任何指点吗?

I know there is no concept of an abstract class in Ruby. But if it needs to be implemented, how do I go about it? I tried something like this:

class A
  def self.new
    raise 'Doh! You are trying to write Java in Ruby!'
  end
end

class B < A
  ...
  ...
end

But, when I try to instantiate B, it is internally going to call A.new which is going to raise the exception.

Also, modules cannot be instantiated, but they cannot be inherited too. Making the new method private will also not work.

Does anyone have any pointers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

蹲在坟头点根烟 2024-07-20 13:53:56

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

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

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

,这应该是关于故事的结局。 在 Java 中使用抽象类的唯一原因是坚持某些方法得到“填充”,而其他方法则在抽象类中具有其行为。 在鸭子类型语言中,重点是方法,而不是类/类型,因此您应该将担忧转移到该级别。

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

Just to chime in late here, I think that there's no reason to stop somebody from instantiating the abstract class, especially because they can add methods to it on the fly.

Duck-typing languages, like Ruby, use the presence/absence or behavior of methods at runtime to determine whether they should be called or not. Therefore your question, as it applies to an abstract method, makes sense

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

and that should be about the end of the story. The only reason to use abstract classes in Java is to insist that certain methods get "filled-in" while others have their behavior in the abstract class. In a duck-typing language, the focus is on methods, not on classes/types, so you should move your worries to that level.

In your question, you're basically trying to recreate the abstract keyword from Java, which is a code-smell for doing Java in Ruby.

夏の忆 2024-07-20 13:53: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 异常。

I don't like using abstract classes in Ruby (there's almost always a better way). If you really think it's the best technique for the situation though, you can use the following snippet to be more declarative about which methods are abstract:

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

Basically, you just call abstract_methods with the list of methods that are abstract, and when they get called by an instance of the abstract class, a NotImplementedError exception will be raised.

蛮可爱 2024-07-20 13:53:56

尝试这个:

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

class B < A
  def initialize
  end
end

Try this:

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

class B < A
  def initialize
  end
end
谁对谁错谁最难过 2024-07-20 13:53:56

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

self.abstract_class = true

For anyone in the Rails world, implementing an ActiveRecord model as an abstract class is done with this declaration in the model file:

self.abstract_class = true
最冷一天 2024-07-20 13:53:56
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

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

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

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

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

In the last 6 1/2 years of programming Ruby, I haven't needed an abstract class once.

If you're thinking you need an abstract class, you're thinking too much in a language that provides/requires them, not in Ruby as such.

As others have suggested, a mixin is more appropriate for things that are supposed to be interfaces (as Java defines them), and rethinking your design is more appropriate for things that "need" abstract classes from other languages like C++.

Update as of 2022: I haven’t needed abstract classes in Ruby in 20 years of use. Everything that all of the folks commenting on my response are saying is addressed by actually learning Ruby and using the appropriate tools, like modules (which even give you common implementations). There are people on teams I have managed who have created classes that have base implementation that fail (like an abstract class), but these are mostly a waste of coding because NoMethodError would produce the exact same result as an AbstractClassError in production.

十六岁半 2024-07-20 13:53:56

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

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

My 2¢: I opt for a simple, lightweight 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

And, of course, adding another error for initializing the base class would be trivial in this case.

瞎闹 2024-07-20 13:53:56

你想用抽象类来达到什么目的? 在 Ruby 中可能有更好的方法,但您没有提供任何细节。

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

What purpose are you trying to serve with an abstract class? There is probably a better way to do it in Ruby, but you didn't give any details.

My pointer is this; use a mixin not inheritance.

白芷 2024-07-20 13:53:56

您可以尝试 3 个红宝石:
接口
摘要
简单摘要

You can try 3 rubygems:
interface
abstract
simple abstract

孤凫 2024-07-20 13:53:56

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

Personally, I raise NotImplementedError in methods of abstract classes. But you may want to leave it out of the new method, for the reasons you mentioned.

仅此而已 2024-07-20 13:53:56

另一个答案:

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

Another answer:

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

This relies on the normal #method_missing to report unimplemented methods,
but keeps abstract classes from being implemented (even if they have an initialize method)

class A
  include Abstract
end
class B < A
end

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

Like the other posters have said, you should probably be using a mixin though, rather than an abstract class.

留一抹残留的笑 2024-07-20 13:53:56

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

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

If you want to go with an uninstantiable class, in your A.new method, check if self == A before throwing the error.

But really, a module seems more like what you want here. For example, Enumerable is the sort of thing that might be an abstract class in other languages. You technically can't subclass them, but calling include SomeModule achieves roughly the same goal. Is there some reason this won't work for you?

时光瘦了 2024-07-20 13:53:56

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

I did it this way, so it redefines new on child class to find a new on non abstract class.
I still don't see any practical in using abstract classes in 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
娜些时光,永不杰束 2024-07-20 13:53:56

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

There's also this small abstract_type gem, allowing to declare abstract classes and modules in an unobstrusive way.

Example (from the README.md file):

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
一曲琵琶半遮面シ 2024-07-20 13:53:56

你的做法没有任何问题。 当然,只要所有子类都重写 initialize ,在初始化程序中引发错误似乎没问题。 但你不想这样定义 self.new 。 这就是我要做的:

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

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

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

There is nothing wrong with your approach. Raising an error in the initializer seems fine, as long as all your subclasses override initialize of course. But you don't want to define self.new like that. Here's what I would do:

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

Another approach would be put all that functionality in a module, which as you mentioned can never be instantiated. Then, include the module in your classes rather than inheriting from another class. However, this would break things like super.

It depends on how you want to structure it, although modules seem like a cleaner solution for solving the problem of "How do I write some stuff that is deigned for other classes to use"

你与清晨阳光 2024-07-20 13:53:56

虽然这感觉不像 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>
>>

Though this doesn't feel like Ruby, you can do this:

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

    puts 'initialized'
  end
end

class B < A
end

The results:

>> A.new
  (rib):2:in `main'
  (rib):2:in `new'
  (rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>
七堇年 2024-07-20 13:53:56

在探索 Ruby 目前可用的两种打字系统时进入此主题

  • Sorbet ( 2020)
  • Ruby 3.x (2021) 中引入了 RBS

如果简单来说Ruby 抽象类看起来像:

# animal.rb
class Animal
  def initialize(name)
    raise "This class is abstract"
  end

  def greet
    puts "My name is #{@name}"
  end
end

# cow.rb
class Cow < Animal
  def initialize(name)
    # super # would raise    
    @name = name
  end
end

# main.rb
animal = Animal.new("Bessie")
# Runtime error: This class is abstract

RBS 中,您可以执行类似下面代码的操作。 然而它只是一个接口——我无法生成一个抽象类:

interface _A
  def a: () -> void
end

# Then we can use this interface in the B class itself
class B
  extend _A
  # ...
end

来源:https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in- action.html

不过,使用 Sorbet,您可以实现等效的抽象类,如下所示:

require 'sorbet-runtime'

# animal.rb
class Animal
  abstract!

  def initialize(name)
    @name = name
  end
  def greet
    puts "My name is #{@name}"
  end
end

# cow.rb
class Cow < Animal
  def initialize(name)
    super
  end
end

# main.rb
animal = Animal.new("Bessie")
# Static error: This class is abstract

此 YouTube 视频解释了这两种解决方案(分钟 25:00)RubyKaigi 2023

Got to this thread while exploring two typing systems currently available in Ruby

  • Sorbet (2020)
  • RBS introduced in Ruby 3.x (2021)

If in plain Ruby the Abstract Class would look like:

# animal.rb
class Animal
  def initialize(name)
    raise "This class is abstract"
  end

  def greet
    puts "My name is #{@name}"
  end
end

# cow.rb
class Cow < Animal
  def initialize(name)
    # super # would raise    
    @name = name
  end
end

# main.rb
animal = Animal.new("Bessie")
# Runtime error: This class is abstract

In RBS you could do something like the code below. However It's only an interface -- I have not been able to gen an Abstract Class:

interface _A
  def a: () -> void
end

# Then we can use this interface in the B class itself
class B
  extend _A
  # ...
end

Source: https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in-action.html

With Sorbet though, you could implement the equivalent Abstract Class like so:

require 'sorbet-runtime'

# animal.rb
class Animal
  abstract!

  def initialize(name)
    @name = name
  end
  def greet
    puts "My name is #{@name}"
  end
end

# cow.rb
class Cow < Animal
  def initialize(name)
    super
  end
end

# main.rb
animal = Animal.new("Bessie")
# Static error: This class is abstract

This youtube video explains both solutions (min 25:00) RubyKaigi 2023

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文