このパターンを抽象化する最良の方法は次のとおりです。
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
は、初期化子の定義時に編集できます。私は違いが何であるかをベンチマークしていません。
new
注: ハッキングよりもハッキングの方が効果的であることがわかりましたinitialize
。メタプログラミングで定義すると、ブロックを代用イニシャライザとしてinitialize
渡すシナリオが発生する可能性があり、ブロック内で使用することはできません。initialize_with
super
これが私の頭に浮かぶ最初の解決策です。私のモジュールには大きな欠点が1つあります。モジュールを含める前に、クラス初期化メソッドを定義する必要があります。そうしないと、機能しません。
その問題にはおそらくもっと良い解決策がありますが、これは私が数分以内に書いたものです。
また、パフォーマンスもあまり考慮していませんでした。特にパフォーマンスについて話すと、おそらく私よりもはるかに優れた解決策を見つけることができます。;)
#!/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