Ruby - установка значений свойств при инициализации объекта
Учитывая следующий класс:
class Test
attr_accessor :name
end
Когда я создаю объект, я хочу сделать следующее:
t = Test.new {Name = 'Some Test Object'}
В настоящий момент это приводит к тому, что имя остается nil
. Возможно ли это?
Примечание. Я не хочу добавлять инициализатор.
Ответы
Ответ 1
нормально
Я придумал решение. Он использует метод initialize, но, с другой стороны, делает именно то, что вы хотите.
class Test
attr_accessor :name
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('@' + key.to_s, val)
end
end
def display
puts @name
end
end
t = Test.new :name => 'hello'
t.display
счастлив?:)
Альтернативное решение с использованием наследования. Обратите внимание, что с этим решением вам не нужно явно объявлять attr_accessor!
class CSharpStyle
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('@' + key.to_s, val)
instance_eval "class << self; attr_accessor :#{key.to_s}; end"
end
end
end
class Test < CSharpStyle
def initialize(arg1, arg2, *init)
super(init.last)
end
end
t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"
Ответ 2
Как упоминалось другими, самый простой способ сделать это - определить метод initialize
. Если вы не хотите этого делать, вы можете наследовать свой класс от Struct.
class Test < Struct.new(:name)
end
Итак, теперь:
>> t = Test.new("Some Test Object")
=> #<struct Test name="Some Test Object">
>> t.name
=> "Some Test Object"
Ответ 3
Существует общий способ выполнения блока инициализации объекта с необходимыми действиями. Этот блок можно оценить в контексте объекта, поэтому у вас есть легкий доступ ко всем переменным экземпляра, методам и т.д.
Продолжая ваш пример
class Test
attr_accessor :name
def initialize(&block)
instance_eval(&block)
end
end
а затем
t = Test.new { @name = 'name' }
или
t = Test.new do
self.name = 'name'
# other initialization, if needed
end
Обратите внимание, что этот способ не требует сложного изменения метода initialize
(который является, по сути, однострочным).
Ответ 4
Как упоминалось ранее, разумный способ сделать это либо с помощью Struct
, либо путем определения метода Test#initialize
. Это именно то, для чего нужны структуры и конструкторы. Использование хеша параметров, соответствующих атрибутам, является ближайшим эквивалентом вашего примера С#, и это обычное соглашение Ruby:
t = Test.new({:name => "something"})
t = Test.new(name: "something") # json-style or kwargs
Но в вашем примере вы делаете то, что больше похоже на присвоение переменной с помощью =
, поэтому попробуйте использовать блок вместо хэша. (Вы также используете Name
, который был бы константой в Ruby, мы это изменим.)
t = Test.new { @name = "something" }
Прохладный, теперь пусть это действительно работает:
class BlockInit
def self.new(&block)
super.tap { |obj| obj.instance_eval &block }
end
end
class Test < BlockInit
attr_accessor :name
end
t = Test.new { @name = "something" }
# => #<Test:0x007f90d38bacc0 @name="something">
t.name
# => "something"
Мы создали класс с конструктором, который принимает блок-аргумент, который выполняется внутри вновь созданного объекта.
Поскольку вы сказали, что хотите избежать использования initialize
, я вместо этого переопределяю new
и вызываю super
, чтобы получить поведение по умолчанию из Object#new
. Обычно мы определяем initialize
вместо, этот подход не рекомендуется, кроме как для удовлетворения конкретного запроса в вашем вопросе.
Когда мы передаем блок в подкласс BlockInit
, мы можем сделать больше, чем просто задавать переменную... мы по сути просто вводим код в метод initialize
(который мы избегаем записи). Если вы также хотели использовать метод initialize
, который делает другие вещи (как вы упомянули в комментариях), вы можете добавить его в Test
и даже не нужно называть super
(так как наши изменения не находятся в BlockInit#initialize
, скорее BlockInit.new
)
Надеюсь, что это творческое решение для очень специфического и интригующего запроса.
Ответ 5
Код, который вы указываете, передает параметры в функцию initialize
. Вам определенно придется либо использовать initialize
, либо использовать более скучный синтаксис:
test = Test.new
test.name = 'Some test object'
Ответ 6
Требуется выполнить подкласс Test (здесь показан собственный метод и инициализатор), например:
class Test
attr_accessor :name, :some_var
def initialize some_var
@some_var = some_var
end
def some_function
"#{some_var} calculation by #{name}"
end
end
class SubClassedTest < Test
def initialize some_var, attrbs
attrbs.each_pair do |k,v|
instance_variable_set('@' + k.to_s, v)
end
super(some_var)
end
end
tester = SubClassedTest.new "some", name: "james"
puts tester.some_function
выходы: some calculation by james
Ответ 7
Вы можете сделать это.
class Test
def not_called_initialize(but_act_like_one)
but_act_like_one.each_pair do |variable,value|
instance_variable_set('@' + variable.to_s, value)
class << self
self
end.class_eval do
attr_accessor variable
end
end
end
end
(t = Test.new).not_called_initialize :name => "Ashish", :age => 33
puts t.name #=> Ashish
puts t.age #=> 33
Одно из преимуществ заключается в том, что вам даже не нужно заранее определять свои переменные экземпляра с помощью attr_accessor
. Вы можете передать все переменные экземпляра, которые вам нужны, с помощью метода not_called_initialize
и создать его, помимо определения геттеров и сеттеров.
Ответ 8
Если вы не хотите переопределять initialize
, вам придется перемещаться по цепочке и переопределять new
. Вот пример:
class Foo
attr_accessor :bar, :baz
def self.new(*args, &block)
allocate.tap do |instance|
if args.last.is_a?(Hash)
args.last.each_pair do |k,v|
instance.send "#{k}=", v
end
else
instance.send :initialize, *args
end
end
end
def initialize(*args)
puts "initialize called with #{args}"
end
end
Если последнее, что вы проходите, это хэш, он обходит initialize
и сразу вызовет сеттеры. Если вы передадите что-нибудь еще, он вызовет инициализацию с этими аргументами.