سؤال

ما هي أفضل طريقة لتجريد هذا النمط:

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 موروثة، ولكن تحديد مجموعة جديدة في فئة فرعية سيؤدي إلى إزالة السمات الأصلية
  • ربما حقق الحل الديناميكي نجاحًا كبيرًا في الأداء

إذا كنت ترغب في إنشاء حل بأقل قدر من الأداء، فلن يكون من الصعب إعادة البناء معظم من الوظيفة إلى سلسلة يمكن أن تكون evaled عندما يتم تعريف المُهيئ.لم أقم بمقارنة ما سيكون عليه الفرق.

ملحوظة:لقد وجدت أن القرصنة new يعمل بشكل أفضل من القرصنة 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"
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top