抽象这种模式的最佳方法是什么:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
@foo, @bar = foo, bar
end
end
一个好的解决方案应该考虑超类并且能够处理仍然能够拥有一个初始化器来做更多的事情。在您的解决方案中不牺牲性能的额外积分。
抽象这种模式的最佳方法是什么:
class MyClass
attr_accessor :foo, :bar
def initialize(foo, bar)
@foo, @bar = foo, bar
end
end
一个好的解决方案应该考虑超类并且能够处理仍然能够拥有一个初始化器来做更多的事情。在您的解决方案中不牺牲性能的额外积分。
该问题的解决方案已经(部分)存在,但是如果您想要在您的类中使用更具声明性的方法,那么以下应该可以工作。
class Class
def initialize_with(*attrs, &block)
attrs.each do |attr|
attr_accessor attr
end
(class << self; self; end).send :define_method, :new do |*args|
obj = allocate
init_args, surplus_args = args[0...attrs.size], args[attrs.size..-1]
attrs.zip(init_args) do |attr, arg|
obj.instance_variable_set "@#{attr}", arg
end
obj.send :initialize, *surplus_args
obj
end
end
end
您现在可以执行以下操作:
class MyClass < ParentClass
initialize_with :foo, :bar
def initialize(baz)
@initialized = true
super(baz) # pass any arguments to initializer of superclass
end
end
my_obj = MyClass.new "foo", "bar", "baz"
my_obj.foo #=> "foo"
my_obj.bar #=> "bar"
my_obj.instance_variable_get(:@initialized) #=> true
该解决方案的一些特点:
initialize_with
initialize
进行自定义初始化super
可以来电initialize
initialize
是没有被指定的属性使用的参数initialize_with
initialize_with
被继承,但在子类上定义新集合将删除父属性如果您想创建一个性能开销绝对最小的解决方案,那么将大部分功能重构为一个可以在eval
定义初始化程序时编辑的字符串并不难。我没有对差异进行基准测试。
注意:我发现 hackingnew
比 hacking 效果更好initialize
。如果您initialize
使用元编程进行定义,您可能会遇到这样一种情况,即您将一个块initialize_with
作为替代初始化程序传递给它,并且不可能super
在一个块中使用。
这是我想到的第一个解决方案。我的模块有一个很大的缺点:您必须在包含模块之前定义类初始化方法,否则它将不起作用。
这个问题可能有更好的解决方案,但这是我在不到几分钟的时间内写的。
另外,我没有过多考虑表演。您可能会找到比我更好的解决方案,尤其是谈论表演。;)
#!/usr/bin/env ruby -wKU
require 'rubygems'
require 'activesupport'
module Initializable
def self.included(base)
base.class_eval do
extend ClassMethods
include InstanceMethods
alias_method_chain :initialize, :attributes
class_inheritable_array :attr_initializable
end
end
module ClassMethods
def attr_initialized(*attrs)
attrs.flatten.each do |attr|
attr_accessor attr
end
self.attr_initializable = attrs.flatten
end
end
module InstanceMethods
def initialize_with_attributes(*args)
values = args.dup
self.attr_initializable.each do |attr|
self.send(:"#{attr}=", values.shift)
end
initialize_without_attributes(values)
end
end
end
class MyClass1
attr_accessor :foo, :bar
def initialize(foo, bar)
@foo, @bar = foo, bar
end
end
class MyClass2
def initialize(*args)
end
include Initializable
attr_initialized :foo, :bar
end
if $0 == __FILE__
require 'test/unit'
class InitializableTest < Test::Unit::TestCase
def test_equality
assert_equal MyClass1.new("foo1", "bar1").foo, MyClass2.new("foo1", "bar1").foo
assert_equal MyClass1.new("foo1", "bar1").bar, MyClass2.new("foo1", "bar1").bar
end
end
end
class MyClass < Struct.new(:foo, :bar)
end
我知道这是一个老问题,答案完全可以接受,但我想发布我的解决方案,因为它利用了Module#prepend
(Ruby 2.2 中的新功能)以及模块也是非常简单解决方案的类这一事实。首先是制作魔法的模块:
class InitializeWith < Module
def initialize *attrs
super() do
define_method :initialize do |*args|
attrs.each { |attr| instance_variable_set "@#{attr}", args.shift }
super *args
end
end
end
end
现在让我们使用我们花哨的模块:
class MyClass
prepend InitializeWith.new :foo, :bar
end
请注意,我留下了我们的attr_accessible
东西,因为我认为这是一个单独的问题,尽管支持它是微不足道的。现在我可以创建一个实例:
MyClass.new 'baz', 'boo'
我仍然可以initialize
为自定义初始化定义一个。如果我的自定义initialize
接受一个参数,那将是提供给新实例的任何额外参数。所以:
class MyClass
prepend InitializeWith.new :foo, :bar
def initialize extra
puts extra
end
end
MyClass.new 'baz', 'boo', 'dog'
在上面的例子@foo='baz'
中,@bar='boo'
它会打印dog
。
我还喜欢这个解决方案的一点是它不会用 DSL 污染全局命名空间。需要此功能的对象可以prepend
。其他人都毫发无损。
此模块允许将 attrs 哈希作为 new() 的选项。您可以将模块包含在具有继承的类中,并且构造函数仍然有效。
我喜欢这个比作为参数的 attr 值列表更好,因为,特别是对于继承的 attrs,我不想试图记住哪个参数是哪个。
module Attrize
def initialize(*args)
arg = args.select{|a| a.is_a?(Hash) && a[:attrs]}
if arg
arg[0][:attrs].each do |key, value|
self.class.class_eval{attr_accessor(key)} unless respond_to?(key)
send(key.to_s + '=', value)
end
args.delete(arg[0])
end
(args == []) ? super : super(*args)
end
end
class Hue
def initialize(transparent)
puts "I'm transparent" if transparent
end
end
class Color < Hue
include Attrize
def initialize(color, *args)
p color
super(*args)
p "My style is " + @style if @style
end
end
你可以这样做:
irb(main):001:0> require 'attrize'
=> true
irb(main):002:0> c = Color.new("blue", false)
"blue"
=> #<Color:0x201df4>
irb(main):003:0> c = Color.new("blue", true, :attrs => {:style => 'electric'})
"blue"
I'm transparent
"My style is electric"