【问题标题】:What is the cleanest way to calculate instance variables in initialize?在初始化中计算实例变量的最简洁方法是什么?
【发布时间】:2013-05-03 23:40:20
【问题描述】:

假设我有一门课,它定义了我的日子的集合以及它们的古怪程度。像这样在初始化函数中初始化我的@scores 变量是否更好:

class WackyDayScorer
  attr_reader :scores
  def initialize(start = Time.now, max = 90.days)
    @start  = start
    @limit  = start - max
    @scores = get_scores
  end

private
  def get_scores
    result = []
    t = @start.clone
    until t < max
      result << score_wackiness(t)
      t -= 1.day
    end
    result
  end
end

或者像这样在 get_scores 方法中初始化它:

class WackyDayScorer
  attr_reader :scores

  def initialize(start = Time.now, max = 90.days)
    @start = start
    @limit = start - max
    get_scores
  end

private
  def get_scores
    @scores = []
    t = @start.clone
    until t < max
      @scores << score_wackiness(t)
      t -= 1.day
    end
  end
end

【问题讨论】:

    标签: ruby coding-style


    【解决方案1】:

    第一个是完全错误的。你在说

    @scores = get_scores
    

    但是get_scores 没有返回有用的值,所以这很疯狂。相反,get_scores 直接在内部将@scores 设置为副作用。

    第二个至少是连贯的。

    不过,就我个人而言,我不会做 任何一个所做的事情。我会说你第一次说的话:

    @scores = get_scores
    

    但我对get_scores 的实现不会触及任何ivar。它会返回一个实际值:

      def get_scores
        scores = []
        # do stuff to scores
        return scores # or simply, scores
      end
    

    【讨论】:

    • 我不会返回任何值,因为惯用的 Ruby 使用方法调用的隐式返回。
    • 对,但是要返回的局部变量值仍然需要在最后的位置才能隐式返回。
    • 修复了您在第一个示例中指出的错误,谢谢。似乎您是说第一个解决方案是您会选择的解决方案。
    • 在您编辑的版本中,是的,完全正确。不幸的是,您的编辑版本将我的回答变成了废话。 :(
    • 哦,我更喜欢第一种方式(现在)的原因是很清楚initialize 正在做什么:它正在设置三个 ivars。第二个,一点都不明显,不看两个地方,是怎么回事。
    【解决方案2】:

    如果要在初始化中处理某些事情,请创建一个私有方法来处理计算,然后在#initialize 中设置实例变量。我不会在方法中设置它,以防你想在类的其他地方重用该计算而不清除你的实例变量。

    class WackyDayScorer
      attr_accessor :scores
    
      def initialize(start = DateTime.now, max = 90.days)
        @start = start
        @limit = start - max
        @scores = calculate_scores
      end
    
    private
    
      def calculate_scores
        (@limit..@start).to_a.map { |date_time| score_wackiness(date_time) }
      end
    end
    

    在#calculate_scores 中,我们在限制日期和开始之间创建一个范围(如您所做的那样倒数)并将每个结果发送到#score_wackiness 方法。

    【讨论】:

    • 我决定不这样做,因为每次有人使用这个类时都会计算这个值。为什么要推迟不可避免的事情?
    【解决方案3】:

    我建议明确定义分数的 getter,记住计算并跳过所有仪式:

    class WackyDayScorer
      def initialize(start = Time.now, max = 90.days)
        @start  = start
        @limit  = start - max
      end
    
      def scores
        @scores ||= begin
          result = []
          t = @start.clone
          until t < max
            result << score_wackiness(t)
            t -= 1.day
          end
          result
        end
      end
    end
    

    这基本上就是你正在做的事情,但它更直接。

    【讨论】:

    • 我喜欢这个。但我不认为我会称之为“记忆”。这只是延迟初始化(通过 getter)。
    • @matt 使用 ||= 并将其分配给实例变量 is 记忆。你可以read more about what memoization is here
    • 我不同意,我认为您没有正确阅读这篇文章。记忆化是将 每个不同的 x 的 f(x) 的值存储在某个昂贵的函数 f 下的技术,这样如果对相同的参数值再次进行相同的调用,它就没有要重新计算。参见例如github.com/djberg96/memoize
    • ||= 只是用于初始化实例变量的标准 ruby​​ 习惯用法
    • 如果我们在没有为x 提供值时将计算结果存储在f(x) 中,这样,当第二次调用f 时,它不会'不必重新计算价值……对我来说似乎是记忆。我在这里错了也没关系,但这是我第一次看到有人争辩说在 Ruby 中使用||=不是记忆。如果||= 真的不是记忆化,谷歌搜索与 Ruby 和记忆化相关的各种短语似乎会发现很多错误信息。
    猜你喜欢
    • 2017-04-25
    • 2017-11-01
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多