我知道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
,这会引发异常。
此外,模块无法实例化,但也无法继承。使新方法私有也行不通。有什么指针吗?
答案 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>
>>