質問

このパターンを抽象化する最善の方法は次のとおりです。

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 カスタム初期化を行うには
  • 通話可能 superinitialize
  • への引数 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"
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top