【问题标题】:Inheritance caching patterns in rubyruby 中的继承缓存模式
【发布时间】:2015-05-20 00:09:39
【问题描述】:

假设我有一个父类:

class Stat

  def val
    raise "method must be implemented by subclass"
  end

end

还有一个子类:

class MyStat < Stat

  def val
    #performs costly calculation and returns value
  end    

end

通过扩展父类,我希望子类不必担心缓存“val”方法的返回值。

这里可以使用很多模式来达到这种效果,我已经尝试了几种尺寸,但没有一个适合我,而且我知道这是一个已解决的问题,所以浪费时间和努力。这通常是如何处理的?

另外,我突然想到我可能问错了问题。也许我根本不应该使用继承,而是使用组合。

任何和所有的想法都表示赞赏。

编辑:

我采用的解决方案可以总结如下:

class Stat

  def value
    @value ||= build_value 
  end

  def build_value
    #to be implemented by subclass
  end

end

【问题讨论】:

  • 抽象基类不是你在 Ruby 中经常看到的东西,没有标准的方法来声明它们,在 raise 中存根是 Ruby 标准的一个非常绝望的举动。通常,您会看到基类实现了 null 情况,或者做的工作尽可能少,文档清楚地表明您的子类必须实现许多事情。对所有方法进行测试是至关重要的,这里没有依赖编译器来捕捉错误。指导人们应该做什么。如果他们不听,不要大惊小怪。
  • @tadman 谢谢,很高兴听到这个消息。我的第一语言是 java,我不禁觉得它对我在其他语言中与设计相关的思维有着有害的影响。红宝石的方式是安静地失败。我只是不禁觉得代码应该是自我记录的,所以如果没有我为它编写的一些冗长的文档,我编写的代码可能没有意义的想法让我感到不安,尽管也许它不应该是。
  • 就像学习一种语言的人带有另一种语言的口音一样,编程时也是如此。 Java 方式和 Ruby 方式经常发生冲突,而 Ruby 更为随意。因此,测试是验证是否已实施正确行为的唯一方法。抽象基类只是解决问题的一种方法,但还有其他方法可以实现相同的目标。就我个人而言,我喜欢 meagar 的回答,它是 Ruby 方式,对您的异常要求稍作让步。

标签: ruby oop inheritance design-patterns


【解决方案1】:

通常我使用简单的模式,而不管是否存在继承:

class Parent
  def val
    @val ||= calculate_val
  end

  def calculate_value
    fail "Implementation missing"
  end
end

class Child < Parent
  def calculate_val
    # some expensive computation
  end
end

我总是喜欢将复杂而昂贵的逻辑包装在自己的方法中,或者不知道它们的返回值将是memoized 的方法中。它为您提供了更清晰的关注点分离;一种方法用于缓存,一种方法用于计算。

它还恰好为您提供了一种覆盖逻辑的好方法,而无需覆盖缓存逻辑。

在上面的简单示例中,memoized 方法val 非常多余。但是它的模式还可以让你记住接受参数的方法,或者当实际缓存不那么琐碎时,保持缓存和计算之间的职责分离:

def is_prime(n)
  @is_prime ||= {}
  @is_prime[n] ||= compute_is_prime
end

【讨论】:

  • 谢谢。实际上我曾经写过这个,但不幸的是我也想缓存方法“to_string”的值,我不喜欢“calculate_string”的声音。我可以说“calculate_value”和“build_string”,但是我会使用不同的语言来描述相同类型的方法。我想我可能最终会到处使用“build_”。
【解决方案2】:

如果您想保持方法名称相同并且不创建新方法来放入逻辑,则在模块前添加模块而不是使用父/子继承。

module MA
  def val
    puts("module's method")
    @_val ||= super
  end
end

class CA
  def val
    puts("class's method")
    1
  end

  prepend MA
end

ca = CA.new
ca.val # will print "module's method" and "class's method". will return 1.
ca.val # will print "module's method". will return 1.

【讨论】:

  • 这很漂亮。看来prepend 有几个有用的用法。
  • 这非常棒,不幸的是我没有完全阐明我的用例,结果证明这对我不起作用。我有一个完整的继承层次结构,有几个层次(StatUnit 是父类,Stat 和 StatSet 从 StatUnit 继承,Stat 和 StatSet 有自己的孩子,等等),如果我在我的顶级类中实现它没有不与该班级的任何孩子一起工作。
  • 在那种情况下,我会选择@meager 的回应。请注意,尽管我不鼓励您在 Ruby 中实现抽象类的 Java 类型。我显然假设Stat(最顶层的父级)是一个抽象类并且它没有在任何地方被实例化。如果是这种情况,那么更像 Ruby 的方法是将Stat 转换为模块(可能是StatMethods)并将其包含在实际实例化的类(或父类)中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-20
  • 1970-01-01
  • 2011-08-14
  • 1970-01-01
  • 2013-10-31
  • 2010-11-08
  • 1970-01-01
相关资源
最近更新 更多