2010-02-27 16 views
17

Dada la siguiente clase:Ruby - Configuración de los valores de propiedad al inicializar objeto

class Test 
    attr_accessor :name 
end 

Al crear el objeto, quiero hacer lo siguiente:

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

En el momento en que se traduce en nombre siendo nil todavía. es posible?

Nota: No deseo agregar un inicializador.

+0

La eliminación de un método de inicialización (¿es eso lo que quiso decir?) Hace las cosas difíciles. Al crear nuevos objetos 'Test', ¿debería' name' obtener la misma inicialización cada vez? ¿O debería obtener un valor especificado en código abierto? – DigitalRoss

+2

La motivación sería hacer algo similar a C# - http://msdn.microsoft.com/en-us/library/bb384062.aspx –

+0

¿Hay algo que no le gusta con la solución que propongo? –

Respuesta

0

El código que está indicando está pasando parámetros a la función initialize. Lo más definitivamente tiene que usar ya sea initialize, o utilizar una sintaxis más aburrido:

test = Test.new 
test.name = 'Some test object' 
9

como han mencionado otros, la manera más fácil de hacer esto sería definir un método initialize. Si no quieres hacer eso, puedes hacer que tu clase herede de Struct.

class Test < Struct.new(:name) 
end 

Así que ahora:

>> t = Test.new("Some Test Object") 
=> #<struct Test name="Some Test Object"> 
>> t.name 
=> "Some Test Object" 
18

bien,

me ocurrió una solución. Utiliza el método de inicialización pero, por otro lado, haz exactamente lo que quieras.

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 

feliz? :)


Solución alternativa usando la herencia. Tenga en cuenta que con esta solución, no necesita declarar explícitamente 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}" 
7

Hay una manera general de hacer bloques de inicialización de objetos complejos con acciones necesarias. Este bloque puede ser evaluada en el contexto del objeto, por lo que tiene fácil acceso a todas las variables de instancia, métodos etc.

Continuando con el ejemplo

class Test 
    attr_accessor :name 

    def initialize(&block) 
    instance_eval(&block) 
    end 
end 

y luego

t = Test.new { @name = 'name' } 

o

t = Test.new do 
    self.name = 'name' 
    # other initialization, if needed 
end 

Tenga en cuenta que de esta manera no se requiere un cambio complejo de initialize método (que es, de hecho, de una sola línea).

0

necesitaría subclase de prueba (aquí mostrado con método propio y inicializador) ej .:

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 

salidas: some calculation by james

0

Usted puede hacer esto.

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 

Una de las ventajas es que no tienen ni siquiera para definir sus variables de instancia por adelantado usando attr_accessor. Puede pasar todas las variables de instancia que necesite a través del método not_called_initialize y dejar que las cree además de definir los getters y los setters.

0

Si no desea anular initialize, deberá subir la cadena y anular new. He aquí un ejemplo:

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 

Si lo último que se pasa en un hash que pasará por alto initialize y llamar a los emisores de inmediato. Si pasa algo más en él llamará a inicializar con esos argumentos.

1

Como se mencionó anteriormente, la forma sensata de hacerlo es con Struct o definiendo un método Test#initialize. Esto es exactamente para lo que son las estructuras y los constructores. Usando un hash de opciones correspondiente a atributos es el equivalente más cercano de su ejemplo de C#, y es una convención Rubí-aspecto normal:

t = Test.new({:name => "something"}) 
t = Test.new(name: "something") # json-style or kwargs 

Pero en su ejemplo que está haciendo algo que se parece más a la asignación de variables utilizando = así que vamos a intente usar un bloque en lugar de un hash. (También está utilizando Name que sería una constante en Ruby, vamos a cambiar eso.)

t = Test.new { @name = "something" } 

fresca, Ahora vamos a hacer que realmente funcionan:

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" 

Hemos creado una clase con un constructor que acepta un argumento de bloque, que se ejecuta dentro del objeto recién instanciado.

Debido a que usted ha dicho que quería evitar el uso de initialize, estoy lugar primordial new y llamando super para obtener el comportamiento por defecto de Object#new. Normalmente definiríamos initialize en lugar de, este enfoque no se recomienda excepto para cumplir con la solicitud específica en su pregunta.

Cuando pasamos un bloque a una subclase de BlockInit podemos hacer algo más que simplemente establecer la variable ... básicamente estamos inyectando código en el método initialize (que estamos evitando escribir). Si también quería un método initialize que hace otras cosas (como usted ha mencionado en comentarios) se podría añadir a Test y ni siquiera tiene que llamar super (ya que nuestros cambios no están en BlockInit#initialize, en lugar BlockInit.new)

la esperanza de que es una solución creativa a una solicitud muy específica e intrigante.

Cuestiones relacionadas