Создание идиоматического объекта в рубине

В рубине я часто нахожу, что пишу следующее:

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

  << more stuff >>

end

или даже

class Foo
  attr_accessor :bar, :baz

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

  << more stuff >>

end

Я всегда стараюсь максимально свести к минимуму шаблон, так что есть ли более идиоматический способ создания объектов в рубине?

Ответ 1

Один из вариантов заключается в том, что вы можете наследовать определение своего класса из 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"

Ответ 2

Я спросил повторяющийся вопрос и предложил свой собственный ответ там, ожидая лучшего, но удовлетворительного не появилось. Я выложу свой собственный.

Определите метод класса, подобный следующему, в духе методов 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

Затем вы можете использовать его следующим образом:

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>

Ответ 3

Struct

Struct объект - это классы, которые делают почти то, что вы хотите. Единственное различие заключается в том, что метод initialize имеет значение nil в качестве значения по умолчанию для всех аргументов. Вы используете его так:

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

или

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

Struct имеет один большой недостаток. Вы не можете наследовать из другого класса.

Напишите свой собственный спецификатор атрибутов

Вы можете написать свой собственный метод для указания атрибутов

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

Теперь ваш класс может наследовать другие классы. Единственный недостаток здесь - вы не можете получить количество аргументов для инициализации. Это то же самое для Struct.

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

Ответ 4

Вы можете использовать Virtus, я не думаю, что это идиоматический способ сделать это, но он делает для вас все плиты котла.

require 'Virtus'

class Foo
  include 'Virtus'

  attribute :bar, Object 
  attribute :baz, Object

end

Затем вы можете делать такие вещи, как

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

Если вам не нравится передавать хэш инициализатору, добавьте:

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

Если вы не думаете, что это достаточно DRY, вы также можете сделать

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

Ответ 5

Я иногда делаю

@bar, @baz = bar, baz

Еще шаблон, но он занимает только одну строку.

Я думаю, вы могли бы также сделать

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

(Я уверен, что есть менее опасный способ сделать это, заметьте)

https://bugs.ruby-lang.org/issues/5825 - это предложение сделать табличку менее подробной.

Ответ 6

Вы можете использовать объект как параметр.

class Foo
  attr_accessor :param

  def initialize(p)
    @param = p
  end
end

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

В этом случае это не сэкономит много строк, но будет, если ваш класс должен обрабатывать большое количество параметров. Вы также можете реализовать метод set_param и get_param, если хотите, чтобы ваш @param var был приватным.