在我的代码库中,我有一堆对象都遵循相同的接口,包括这样的东西:
class MyTestClass
def self.perform(foo, bar)
new(foo, bar).perform
end
def initialize(foo, bar)
@foo = foo
@bar = bar
end
def perform
# DO SOMETHING AND CHANGE THE WORLD
end
end
类之间的区别因素是self.perform
和initialize
的arity,以及#perform
类的主体。
所以,我希望能够创建一个ActiveSupport::Concern
(或者只是一个常规的Module
,如果这会更好),这让我做了这样的事情:
class MyTestClass
inputs :foo, :bar
end
然后将使用一些元编程来定义上述方法的self.perform
和initialize
,其中airty将取决于self.inputs
方法指定的airty。
这是我到目前为止:
module Commandable
extend ActiveSupport::Concern
class_methods do
def inputs(*args)
@inputs = args
class_eval %(
class << self
def perform(#{args.join(',')})
new(#{args.join(',')}).perform
end
end
def initialize(#{args.join(',')})
args.each do |arg|
instance_variable_set(@#{arg.to_s}) = arg.to_s
end
end
)
@inputs
end
end
end
这似乎得到了方法的正确性,但我很难搞清楚如何处理#initialize
方法的主体。
任何人都可以帮我找出一种方法,我可以成功地编程#initialize
的身体,所以它的行为就像我提供的例子?
你真是太近了! instance_variable_set
有两个参数,第一个是实例变量,第二个是你想要设置它的值。您还需要获取变量的值,您可以使用send
来完成。
instance_variable_set(@#{arg.to_s}, send(arg.to_s))
你可以使用它作为#initialize
的主体:
#{args}.each { |arg| instance_variable_set("@\#{arg}", arg) }
但是,我不会将它评估为eval。它通常导致邪恶的事情。也就是说,这是一个实现,它给出了一个不正确的Foo.method(:perform).arity
,但仍然表现得像你期望的那样:
module Commandable
def inputs(*arguments)
define_method(:initialize) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
arguments.zip(parameters).each do |argument, parameter|
instance_variable_set("@#{argument}", parameter)
end
end
define_singleton_method(:perform) do |*parameters|
unless arguments.size == parameters.size
raise ArgumentError, "wrong number of arguments (given #{parameters.size}, expected #{arguments.size})"
end
new(*parameters).perform
end
end
end
class Foo
extend Commandable
inputs :foo, :bar
def perform
[@foo, @bar]
end
end
Foo.perform 1, 2 # => [1, 2]