Подтвердить что ты не робот

Ruby - установка значений свойств при инициализации объекта

Учитывая следующий класс:

class Test
  attr_accessor :name
end

Когда я создаю объект, я хочу сделать следующее:

t = Test.new {Name = 'Some Test Object'}

В настоящий момент это приводит к тому, что имя остается nil. Возможно ли это?

Примечание. Я не хочу добавлять инициализатор.

4b9b3361

Ответ 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 и сразу вызовет сеттеры. Если вы передадите что-нибудь еще, он вызовет инициализацию с этими аргументами.