如何在 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
,这将引发异常。
此外,模块不能被实例化,但也不能被继承。 将新方法设为私有也不起作用。
有人有任何指点吗?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(18)
只是为了在这里插话,我认为没有理由阻止某人实例化抽象类, 特别是因为他们可以动态添加方法。
鸭子类型语言(例如 Ruby)使用运行时方法的存在/不存在或行为来确定是否应该调用它们。 因此,你的问题,因为它适用于抽象方法,是有道理的
,这应该是关于故事的结局。 在 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
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.我不喜欢在 Ruby 中使用抽象类(几乎总有更好的方法)。 如果您确实认为这是适合这种情况的最佳技术,您可以使用以下代码片段来更明确地说明哪些方法是抽象的:
基本上,您只需使用抽象方法列表调用
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:
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, aNotImplementedError
exception will be raised.尝试这个:
Try this:
对于 Rails 世界中的任何人来说,将 ActiveRecord 模型实现为抽象类是通过模型文件中的以下声明完成的:
For anyone in the Rails world, implementing an ActiveRecord model as an abstract class is done with this declaration in the model file:
在过去 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 anAbstractClassError
in production.我的 2 美分:我选择一个简单、轻量级的 DSL mixin:
当然,在这种情况下,添加另一个初始化基类的错误是微不足道的。
My 2¢: I opt for a simple, lightweight DSL mixin:
And, of course, adding another error for initializing the base class would be trivial in this case.
你想用抽象类来达到什么目的? 在 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.
您可以尝试 3 个红宝石:
接口
摘要
简单摘要
You can try 3 rubygems:
interface
abstract
simple abstract
就我个人而言,我在抽象类的方法中引发
NotImplementedError
。 但出于您提到的原因,您可能希望将其排除在new
方法之外。Personally, I raise
NotImplementedError
in methods of abstract classes. But you may want to leave it out of thenew
method, for the reasons you mentioned.另一个答案:
这依赖于正常的
#method_missing
来报告未实现的方法,但阻止抽象类被实现(即使它们有一个初始化方法)
就像其他海报所说的那样,您可能应该使用 mixin,而不是抽象类。
Another answer:
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)
Like the other posters have said, you should probably be using a mixin though, rather than an abstract class.
如果您想使用不可实例化的类,请在 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?我这样做了,所以它重新定义了子类的 new 以找到非抽象类的 new 。
我仍然没有看到在 ruby 中使用抽象类有任何实际意义。
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.
还有这个小的
abstract_type
gem,允许以一种不显眼的方式声明抽象类和模块。示例(来自 README.md 文件):
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):
你的做法没有任何问题。 当然,只要所有子类都重写
initialize
,在初始化程序中引发错误似乎没问题。 但你不想这样定义 self.new 。 这就是我要做的:另一种方法是将所有功能放入一个模块中,正如您提到的,该模块永远无法实例化。 然后,将该模块包含在您的类中,而不是从另一个类继承。 然而,这会破坏像
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 defineself.new
like that. Here's what I would do: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"
虽然这感觉不像 Ruby,但您可以这样做:
结果:
Though this doesn't feel like Ruby, you can do this:
The results:
2 行 gem : https://rubygems.org/gems/abstract
2-lines gem : https://rubygems.org/gems/abstract
在探索 Ruby 目前可用的两种打字系统时进入此主题
如果简单来说Ruby 抽象类看起来像:
在 RBS 中,您可以执行类似下面代码的操作。 然而它只是一个接口——我无法生成一个抽象类:
来源:https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in- action.html
不过,使用 Sorbet,您可以实现等效的抽象类,如下所示:
此 YouTube 视频解释了这两种解决方案(分钟 25:00)RubyKaigi 2023
Got to this thread while exploring two typing systems currently available in Ruby
If in plain Ruby the Abstract Class would look like:
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:
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:
This youtube video explains both solutions (min 25:00) RubyKaigi 2023