2009-11-22 11 views
14

En Ruby, a menudo me encuentro escribiendo lo siguiente:creación de objetos idiomáticas en rubí

class Foo 
    def initialize(bar, baz) 
    @bar = bar 
    @baz = baz 
    end 

    << more stuff >> 

end 

o incluso

class Foo 
    attr_accessor :bar, :baz 

    def initialize(bar, baz) 
    @bar = bar 
    @baz = baz 
    end 

    << more stuff >> 

end 

Siempre estoy dispuesto a minimizar repetitivo tanto como sea posible - por lo que es Hay una forma más idiomática de crear objetos en rubí?

+0

Véase también: http: // stacko verflow.com/questions/12763016/how-to-cleanly-initialize-attributes-in-ruby-with-new –

Respuesta

11

Una opción es que se puede heredar su definición de clase de Struct:

class Foo < Struct.new(:bar, :baz) 
    # << more stuff >> 
end 

f = Foo.new("bar value","baz value") 
f.bar #=> "bar value" 
f.baz #=> "baz value" 
+3

En lugar de subclasificar, también puede hacer 'Foo = Struct.new (: bar,: baz) do ... end' – sepp2k

+0

¿Puedes darnos un ejemplo, sepp2k? – Geo

+1

Interesante ... pero ¿y si quieres que Foo clasifique algo más? – grifaton

-1

Se podría utilizar un objeto como parámetro.

class Foo 
    attr_accessor :param 

    def initialize(p) 
    @param = p 
    end 
end 

f = Foo.new 
f.param.bar = 1 
f.param.bax = 2 

Esto no ahorrar mucho líneas en este caso pero lo hará si su clase tiene que manejar un gran número de parámetro. También puede implementar un método set_param y get_param si desea mantener su @param var en privado.

2

Struct

Struct objeto de son clases que hacer casi lo que quiere. La única diferencia es que el método de inicialización no tiene valor predeterminado para todos sus argumentos. Que lo utilice como esto

A= Struct.new(:a, :b, :c) 

o

class A < Struc.new(:a, :b, :c) 
end 

Struct tiene un gran inconveniente. No puedes heredar de otra clase.

Escribe tu propio atributo especificador

Usted puede escribir su propio método para especificar atributos

def attributes(*attr) 
    self.class_eval do 
    attr.each { |a| attr_accessor a } 
    class_variable_set(:@@attributes, attr) 

     def self.get_attributes 
     class_variable_get(:@@attributes) 
     end 

     def initialize(*vars) 
     attr= self.class.get_attributes 
     raise ArgumentError unless vars.size == attr.size 
     attr.each_with_index { |a, ind| send(:"#{a}=", vars[ind]) } 
     super() 
     end 
    end 
end 

class A 
end 

class B < A 
    attributes :a, :b, :c 
end 

Ahora su clase puede heredar de otras clases. El único inconveniente aquí es que no se puede obtener el número de argumentos para inicializar. Esto es lo mismo para Struct.

B.method(:initialize).arity # => -1 
1

veces hago

@bar, @baz = bar, baz 

Aún repetitivo, pero sólo se necesita una línea hacia arriba.

supongo que también podría hacer

["bar", "baz"].each do |variable_name| 
    instance_variable_set(:"@#{variable_name}", eval(variable_name)) 
end 

(Estoy seguro de que hay una forma menos peligrosa de hacer eso, que conste)

https://bugs.ruby-lang.org/issues/5825 es una propuesta para hacer que el texto modelo menos detallado.

10

Pedí a duplicate question, y sugerí mi propia respuesta allí, esperando una mejor, pero una satisfactoria no apareció. Voy a publicar el mío.

Defina un método de clase como el siguiente siguiendo el espíritu de los métodos attr_accessor, attr_reader, attr_writer.

class Class 
    def attr_constructor *vars 
     define_method("initialize") do |*vals| 
      vars.zip(vals){|var, val| instance_variable_set("@#{var}", val)} 
     end 
    end 
end 

A continuación, se puede utilizar de esta manera:

class Foo 
    attr_constructor :foo, :bar, :buz 
end 

p Foo.new('a', 'b', 'c')  # => #<Foo:0x93f3e4c @foo="a", @bar="b", @buz="c"> 
p Foo.new('a', 'b', 'c', 'd') # => #<Foo:0x93f3e4d @foo="a", @bar="b", @buz="c"> 
p Foo.new('a', 'b')   # => #<Foo:0x93f3e4e @foo="a", @bar="b", @buz=nil> 
+0

Interesante, me gusta! – grifaton

+0

@grifaton Gracias. – sawa

2

Usted podría utilizar Virtus, no creo que es la forma idiomática para hacerlo, pero lo hace toda la placa de la caldera para usted.

require 'Virtus' 

class Foo 
    include 'Virtus' 

    attribute :bar, Object 
    attribute :baz, Object 

end 

A continuación, puede hacer cosas como

foo = Foo.new(:bar => "bar") 
foo.bar # => bar 

Si no te gusta pasar un hash para el inicializador luego añadir:

def initialize(bar, baz) 
    super(:bar => bar, :baz => baz) 
end 

Si usted no cree que es SECO suficiente, también puede hacer

def initialize(*args) 
    super(self.class.attributes.map(&:name).zip(args)]) 
end