如何在每个对象类型级别创建接口?

How to create an interface on a per object type level?

在我的 rails (4.2.1) 应用程序中,我有一个类型(模型),其中包含 :name 为“string”、“integer”等的记录

我希望用户能够传入值并检查它是否是给定类型的有效对象。所以,伪代码是:

check_value(:integer, "1") #=> true
check_value(:integer, "foo") #=>false

我想随着时间的推移添加新类型,这些类型有自己的逻辑 check_value 该类型。

以下是我看过的几个备选方案:

1 将每种类型的一种方法直接添加到类型模型中 -

# app/models/type.rb
# inside class Type...
def check_string_value(val)
 true
end

def integer_value(val)
 begin
  Integer(val)
 rescue
  return false
 end
 return true    
end

这可行,但需要我在每次添加新字段类型时修改 type.rb 文件,我希望避免这种情况。

每个类型文件中每个对象方法 2 个:

# lib/types/integer_type/integer_type.rb
int = Type.where(name: "integer").first
class << int
  def check_value(val)
    begin
     Integer(val)
    rescue
     return false
    end
    return true   
  end
end

这个问题是我无法调用整数类型的特定实例来传递验证调用,因为我没有在我的调用代码中构造它。

因此,这些似乎都不理想 - 我想要一种技术,将来自 type.rb 的验证调用委托给要处理的单个类型。这可能吗?我该怎么做?

在 Ruby 中有多种方法可以做到这一点。这是一种非常基本的方法。让每个类型模块定义一个 check 方法,然后使用 Type 模块定义 "register" 本身,例如Type.register(:integer, IntegerType)。然后 Type.check(type, value) 只需要检查注册的类型,如果匹配,则委托给它的 check 方法:

type.rb

module Type
  @@checkers = {}

  def self.check(type, value)
    if @@checkers.key?(type)
      @@checkers[type].check(value)
    else
      raise "No registered type checker for type `#{type}'"
    end
  end

  def self.register(type, mod)
    @@checkers[type] = mod
  end

  def self.registered_types
    @@checkers.keys
  end

  def self.load_types!
    Dir['./types/*.rb'].each do |file|
      require file
    end
  end
end

Type.load_types!

types/integer.rb

module Type
  module Integer
    def self.check(value)
      !!Integer(value)
    rescue ArgumentError
      false
    end
  end
end

Type.register(:integer, Type::Integer)

types/string.rb

module Type
  module String
    def self.check(value)
      true
    end
  end
end

Type.register(:string, Type::String)

然后...

p Type.registered_types # => [ :integer, :string ]
p Type.check(:integer, "1") # => true
p Type.check(:integer, "a") # => false
p Type.check(:string, "a") # => true

当然,您可以使用元编程比这更有趣(请参阅此答案的先前修订版,了解使用 Module#extend 而不是将已注册模块保存在简单哈希中的解决方案)或者说,懒惰加载,但你明白了。 "core" 类型模块不必知道其他模块的名称(您可以根据需要定义 load_types!,或者完全在其他地方定义)。唯一的要求是模块响应 "check_#{type}".