質問
このパターンを抽象化する最善の方法は次のとおりです。
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つの大きな欠点があります:あなたは、モジュールを含む前に、クラスのinitializeメソッドを定義しなければならないか、それが動作しません。
があり、この問題のためのよりよい解決策は、おそらくですが、これは私が数分も経たないうちに書いたものです。
また、私は考慮にあまりにも多くのパフォーマンスを維持しませんでした。おそらく、特に公演の話、私よりもはるかに優れた解決策を見つけることができます。 ;)
#!/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
(ルビー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をハッシュすることができます()。あなたは、継承とクラスのモジュールを含むことができ、およびコンストラクタはまだ動作します。
特に継承ATTRSと、私はこれだったのparam思い出そうと好きではないだろうので、私は、パラメータとしてattrの値のリストよりも、この方が好き。
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"