在Ruby中模拟抽象类(Rails)

时间:2011-01-06 21:15:51

标签: ruby-on-rails ruby ruby-on-rails-3

我想在Ruby on Rails中模拟一个抽象类。即如果有人试图致电Abstract.new,我想提出异常,但他应该可以致电Child.newChild < Abstract)。

怎么做?覆盖newinitialize都不起作用。

4 个答案:

答案 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
警告:我不确定这是否是一个有效的解决方案。这会在基类中隐藏newallocate并在子类中重新启用它们 - 但仅此一项似乎不会阻止使用create!创建对象。在type上添加验证可防止创建基类。我猜你也可以隐藏create!,但我不确定是否涵盖了Rails可以实例化模型对象的所有方式。