Ruby: добавьте пользовательские свойства в встроенные классы
-
25-09-2019 - |
Вопрос
Вопрос:Используя Ruby, просто добавить пользовательские методы к существующим классам, но как вы добавляете пользовательские характеристики? Вот пример того, что я пытаюсь сделать:
myarray = Array.new();
myarray.concat([1,2,3]);
myarray._meta_ = Hash.new(); # obviously, this wont work
myarray._meta_['createdby'] = 'dreftymac';
myarray._meta_['lastupdate'] = '1993-12-12';
## desired result
puts myarray._meta_['createdby']; #=> 'dreftymac'
puts myarray.inspect() #=> [1,2,3]
Цель состоит в том, чтобы построить определение класса таким образом, чтобы материал, который не работает в примере выше, будет работать, как ожидалось.
Обновлять: (Уточнить вопрос) Один аспект, который был исключен из исходного вопроса: также является целью добавления «значений по умолчанию», которые обычно будут настроены в инициализация метод класса.
Обновлять: (Зачем это) нормально, очень просто просто создать пользовательский класс, который наследует от массива (или любого встроенного класса, который вы хотите подражать). Этот вопрос вытекает из какого-либо «только тестирования» кода, и не пытается игнорировать этот вообще приемлемый подход.
Решение
Напомним, что в Ruby у вас нет доступа к атрибутам (переменные экземпляра) вне этого экземпляра. У вас есть доступ только к публичным методам экземпляра.
Вы можете использовать attr_accessor
Чтобы создать метод для класса, который действует как свойство, как вы описываете:
irb(main):001:0> class Array
irb(main):002:1> attr_accessor :_meta_
irb(main):003:1> end
=> nil
irb(main):004:0>
irb(main):005:0* x = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> x._meta_ = Hash.new
=> {}
irb(main):007:0> x._meta_[:key] = 'value'
=> "value"
irb(main):008:0>
Для простого способа сделать инициализацию по умолчанию для Accessior, нам понадобится в основном перерыв attr_accessor
сами:
class Class
def attr_accessor_with_default accessor, default_value
define_method(accessor) do
name = "@#{accessor}"
instance_variable_set(name, default_value) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("@#{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_, {}
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
Но ждать! Выход неверный:
{:key=>"value"}
{:foo=>"bar", :key=>"value"}
Мы создали закрытие вокруг значения по умолчанию буквального хеша.
Лучший способ, возможно, просто использовать блок:
class Class
def attr_accessor_with_default accessor, &default_value_block
define_method(accessor) do
name = "@#{accessor}"
instance_variable_set(name, default_value_block.call) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("@#{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_ do Hash.new end
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
Теперь вывод правильный, потому что Hash.new
Вызывается каждый раз, когда значение по умолчанию извлекается, в отличие от повторного использования того же буквального хеша каждый раз.
{:key=>"value"}
{:foo=>"bar"}
Другие советы
Разве недвижимость не просто добыча и сеттер? Если это так, не могли бы вы просто сделать:
class Array
# Define the setter
def _meta_=(value)
@_meta_ = value
end
# Define the getter
def _meta_
@_meta_
end
end
Тогда вы можете сделать:
x = Array.new
x._meta_
# => nil
x._meta_ = {:name => 'Bob'}
x._meta_
# => {:name => 'Bob'}
Это помогает?