我想在Ruby on Rails中模拟一个抽象类。即如果有人试图致电Abstract.new
,我想提出异常,但他应该可以致电Child.new
(Child < Abstract
)。
怎么做?覆盖new
和initialize
都不起作用。
答案 0 :(得分:10)
在另一条评论中,OP提到抽象类的目的是分享其子代所需的行为(方法)。在Ruby中,通常最好使用一个模块,用于在需要时“混合”方法。例如,而不是:
class Abstract
def foo
puts "foo!"
end
end
class Concrete
end
Concrete.new.foo # => "foo!"
这样:
module Foo
def foo
puts "foo!"
end
end
class Concrete
include Foo
end
Concrete.new.foo # => "foo!"
但这是原始请求可能得到满足的方式:
#!/usr/bin/ruby1.8
class Abstract
def initialize(*args)
raise if self.class == Abstract
super
end
end
class Concrete < Abstract
end
Concrete.new # OK
Abstract.new # Raises an exception
答案 1 :(得分:7)
你为什么要这样做?抽象/接口类的要点是将强类型语言破解成动态范例。如果你需要你的类适合签名,根据原始类命名你的方法或制作一个外观并插入它,不需要欺骗编译器允许它,它只是工作。
def my_printer obj
p obj.name
end
所以我将接口定义为具有name属性
的任何对象class person
attr_accessor :name
def initialize
@name = "Person"
end
end
class Employee
attr_accessor :name
def initialize
@name = "Employee"
@wage = 23
end
end
所以没有什么能阻止我们用这些
调用我们的打印机方法my_printer Person.new
my_printer Employee.new
两者都打印出名字:D
答案 2 :(得分:1)
你几乎总是需要这样做来强制执行一个API,当一些第三方要实现一些存根时,你确定他们会搞砸它。您可以在父类中使用特定的前缀模板,并在创建时使用模块来实现此目的:
module Abstract
def check
local = self.methods - Object.methods
templates = []
methods = []
local.each do |l|
if l =~ /abstract_(.*)/ # <--- Notice we look for abstract_* methods to bind to.
templates.push $1
end
methods.push l.to_s
end
if !((templates & methods) == templates)
raise "Class #{self.class.name} does not implement the required interface #{templates}"
end
end
end
class AbstractParent
include Abstract
def initialize
check
end
def abstract_call # <--- One abstract method here
end
def normal_call
end
end
class Child < AbstractParent # <-- Bad child, no implementation
end
class GoodChild < AbstractParent
def call # <-- Good child, has an implementation
end
end
测试:
begin
AbstractParent.new
puts "Created AbstractParent"
rescue Exception => e
puts "Unable to create AbstractParent"
puts e.message
end
puts
begin
Child.new
puts "Created Child"
rescue Exception => e
puts "Unable to create Child"
puts e.message
end
puts
begin
GoodChild.new
puts "Created GoodChild"
rescue Exception => e
puts "Unable to create GoodChild"
puts e.message
end
结果:
[~] ruby junk.rb
Unable to create AbstractParent
Class AbstractParent does not implement the required interface ["call"]
Unable to create Child
Class Child does not implement the required interface ["call"]
Created GoodChild
答案 3 :(得分:0)
如果您想要进行STI,可以按照this thread中的建议进行操作:
class Periodical < ActiveRecord::Base
private_class_method :new, :allocate
validates_presence_of :type
end
class Book < Periodical
public_class_method :new, :allocate
end
class Magazine < Periodical
public_class_method :new, :allocate
end
警告:我不确定这是否是一个有效的解决方案。这会在基类中隐藏new
和allocate
并在子类中重新启用它们 - 但仅此一项似乎不会阻止使用create!
创建对象。在type
上添加验证可防止创建基类。我猜你也可以隐藏create!
,但我不确定是否涵盖了Rails可以实例化模型对象的所有方式。