أفضل طريقة لتجريد سمات التهيئة
-
21-08-2019 - |
سؤال
ما هي أفضل طريقة لتجريد هذا النمط:
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
ed عندما يتم تعريف المُهيئ.لم أقم بمقارنة ما سيكون عليه الفرق.
ملحوظة:لقد وجدت أن القرصنة 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"