【问题标题】:How to cleanly initialize attributes in Ruby with new?如何用 new 干净地初始化 Ruby 中的属性?
【发布时间】:2012-10-06 19:17:10
【问题描述】:
class Foo
  attr_accessor :name, :age, :email, :gender, :height

  def initalize params
    @name = params[:name]
    @age = params[:age]
    @email = params[:email]
    .
    .
    .
  end

这似乎是一种愚蠢的做法。在 Ruby 中初始化对象的更好/更惯用的方法是什么?

Ruby 1.9.3

【问题讨论】:

  • 我实际上认为这个问题的答案是“不”——可能有更好的方法,但如果有更好的更惯用的方法,那么肯定会有下面投票赞成的答案。

标签: ruby object new-operator ruby-1.9.3


【解决方案1】:

您可以只遍历键并调用设置器。我更喜欢这个,因为如果你传递一个无效的密钥,它会捕获。

class Foo
  attr_accessor :name, :age, :email, :gender, :height

  def initialize params = {}
    params.each { |key, value| send "#{key}=", value }
  end
end

foo = Foo.new name: 'Josh', age: 456
foo.name  # => "Josh"
foo.age   # => 456
foo.email # => nil

【讨论】:

  • 我也喜欢这个,但它似乎只在所有实例变量都有设置器的类中有用。
【解决方案2】:
def initialize(params)
  params.each do |key, value|
    instance_variable_set("@#{key}", value)
  end
end

【讨论】:

  • 我一般不喜欢这个,但如果你要使用它,至少调用setter而不是设置实例变量。如果你用 ivars 弄乱了变量的名字,你不会知道,但是使用方法,你会得到一个 NoMethodError。
  • @Joshua:这完全正确。我同意您的方法通常更可取,但是,我并不总是为所有实例变量提供设置器(例如,当它们在对象初始化后不应更改时)。
  • 大声笑,甚至没有意识到我已经在这个帖子中回答了。无论如何,我几乎总是通过 setter/getter 访问。如果您不希望它们更改,则将它们声明为私有 (example) 但实际上,像这样的初始化程序代码应该只用于数据结构,所以这个问题在实践中不应该出现。
【解决方案3】:

利用 Joshua Cheek 的回答进行一些概括

module Initializable
  def initialize(params = {})
    params.each do |key, value|
      setter = "#{key}="
      send(setter, value) if respond_to?(setter.to_sym, false)
    end
  end
end

class Foo
  include Initializable

  attr_accessor :name, :age, :email, :gender, :height
end

Foo.new name: 'Josh', age: 456
=> #<Foo:0x007fdeac02ecb0 @name="Josh", @age=456>

注意如果已经使用了初始化混合并且我们需要自定义初始化,我们只需调用 super:

class Foo
  include Initializable

  attr_accessor :name, :age, :email, :gender, :height, :handler

  def initialize(*)
    super

    self.handler = "#{self.name} #{self.age}"
  end
end

Foo.new name: 'Josh', age: 45
=> #<Foo:0x007fe94c0446f0 @name="Josh", @age=45, @handler="Josh 45"> 

【讨论】:

    【解决方案4】:
    Foo = Struct.new(:name, :age, :email, :gender, :height)
    

    这对于一个功能齐全的类来说已经足够了。演示:

    p Foo.class # Class
    
    employee = Foo.new("smith", 29, "smith@foo.com", "m", 1.75) #create an instance
    p employee.class # Foo
    p employee.methods.sort # huge list which includes name, name=, age, age= etc
    

    【讨论】:

    • 请注意,这与include ActiveModel::Model结合使用效果不佳。
    【解决方案5】:

    为什么不直接明确指定一个实际的参数列表?

    class Foo
      attr_accessor :name, :age, :email, :gender, :height
    
      def initialize(name, age, email, gender, height)
        @name = name
        @age = age
        @email = email
        @gender = gender
        @height = height
      end
    end
    

    此版本的代码行数可能比其他版本多,但它更容易利用内置语言功能(例如,参数的默认值或在以不正确的数量调用 initialize 时引发错误)。

    【讨论】:

    • 位置参数可能会令人讨厌,原因有很多 - 难以重构 - 并且很容易与传递的变量不匹配。
    • 是的。我更喜欢尽可能明确,所以我喜欢这种方法。这是迄今为止最容易推理的代码。位置参数确实使以后更改签名变得更加困难,但这是我愿意做出的权衡。
    【解决方案6】:

    使用 params 中的所有键是不正确的,您可以定义非自愿名称。我认为它应该是一个名字的白名单

    class Foo
       @@attributes = [:name, :age, :email, :gender, :height]  
    
       @@attributes.each do |attr|
         class_eval { attr_accessor "#{attr}" }
       end  
    
       def initialize params
         @@attributes.each do |attr|
           instance_variable_set("@#{attr}", params[attr]) if params[attr]
         end  
       end
    end
    
    Foo.new({:name => 'test'}).name #=> 'test'
    

    【讨论】:

    • 您将如何使用get_attributes 而不是@@attributes = [:name, :age, :email, :gender, :height]?它们已经用attr_accessor :name, :age, :email, :gender, :height 定义。
    • 你也可以attr_accessor *@@attributes。这里的白名单很有意义。
    【解决方案7】:

    如果你接收一个哈希作为唯一的参数,为什么不把它作为一个实例变量呢?每当您需要一个值时,请从哈希中调用它。您可以保持实例变量名称简短,以便于调用。

    class Foo
      attr_reader :p
      def initalize p
        @p = p
      end
      def foo
        do_something_with(@p[:name])
        ...
      end
    end
    

    如果@p[:name] 对您来说仍然太长,那么您可以将 proc 保存为实例变量,然后调用相关值,例如 @p.(:name)

    class Foo
      attr_reader :p
      def initialize p
        @p = ->x{p[x]}
      end
      def foo
        do_something_with(@p.(:name))
        ...
      end
    end
    

    或者,另一种方法是定义一个调用散列并应用密钥的方法。

    class Foo
      def initalize p
        @p = p
      end
      def get key
        @p[key]
      end
      def foo
        do_something_with(get(:name))
        ...
      end
    end
    

    如果要设置值,你可以定义一个setter方法,如果你愿意,可以进一步检查无效键。

    class Foo
      Keys = [:name, :age, :email, :gender, :height]
      def initalize p
        raise "Invalid key in argument" unless (p.keys - Keys).empty?
        @p = p
      end
      def set key, value
        raise "Invalid key" unless Keys.key?(key)
        @p[key] = value
      end
      def get key
        @p[key]
      end
      def foo
        do_something_with(get(:name))
        ...
      end
    end
    

    【讨论】:

    • 这会给您留下可能的杂散引用:h = { :a =&gt; :b }; Foo.new(h); h[:a] = :nonsense
    猜你喜欢
    • 2012-09-23
    • 2020-09-25
    • 1970-01-01
    • 2013-07-12
    • 2012-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多