清洁面向对象的代码

时间:2016-07-16 15:57:23

标签: ruby oop coding-style

我正在努力成为一名更好的开发者,我总是问自己,如果有更好的方法来做这些事情。这不是我第一次处理这个问题所以我决定问你对它的看法。

假设我必须实现一个代表产品的类。

class Product 
  def initialize (name, net_price)
    @name = name 
    @net_price = net_price
    @gross_price = nil
  end

  def set_gross_price
    @gross_price = heavy_gross_price_calculation
  end

  def export
    @gross_price.nil? && set_gross_price
    return product.to_hash
  end

  def heavy_gross_price_calculation
    #  This function calculate the gross price but let's say that this is 
    #  pretty onerous operation that involves maybe also an external API
    #  request
  end
end

假设这个类的工作流程是创建一个产品,计算总价并将其导出以备将来使用。 不调用set_gross_price中的initialize方法是否正确? 事实是,当您导出产品时,必须计算总价格,但我不认为正确的选择是强迫开发人员在set_gross_price之前调用export,但我也不是确定export方法的第一行,因为该集应关注设置总价而不检查它是否为空。 你有更好的方法来实现这个吗?

由于

3 个答案:

答案 0 :(得分:0)

我看到了几种方法:

  • Product成为一个愚蠢的对象,只保存有关产品的信息,从外部计算价格
  • 使gross_price成为 memoizes 计算结果的方法

愚蠢对象

class Product
  def initialize(name, net_price, gross_price)
    @name ...
    ...
  end
end

class GrossPriceCalculator
  def initialize()

  end

  def call(net_price)
    # complicate operation goes here
    # ...
  end
end

<强>记忆化

class Product
  def initialize(name, net_price)
    @name = name
    @net_price = net_price
  end

  def gross_price
    @gross_price ||= calculate_gross_price
  end

  private

  def calculate_gross_price
    # here is the complicate operation..
    #
  end
end

我更喜欢第一种解决方案。它不会触发由某些完全不同的操作(如导出)隐藏的潜在昂贵的调用

答案 1 :(得分:0)

完全同意如果出口需要繁重的计算,那么你不应该在初始化中做到这一点(作为具体案例,考虑编写单元测试;为什么所有的测试都要付出沉重的代价单个方法需要时计算?)。

避免@gross_price.nil? && set_gross_price之类的事情的一种方法是,如果变量为空,则使用||=执行一次性分配(请参阅例如What does ||= (or-equals) mean in Ruby?)。同时,通过使用属性访问器,您可以在执行计算时抽象自己,并在getter上进行惰性计算:

class Product

  attr_reader :name, :net_price

  def initialize (name, net_price)
    @name = name
    @net_price = net_price
    @gross_price = nil
  end

  def gross_price
    @gross_price ||= heavy_gross_price_calculation
  end

  def export
    return to_hash
  end

  def heavy_gross_price_calculation
    puts "heavy calculation"
    1000
  end

  def to_hash
    Hash[instance_variables.map do |var_name|
      # Remove the "@"
      name = var_name.to_s[1..-1]
      [name, send(name)]
    end]
  end

end

我在这里给出了一个可能的to_hash实现,但当然你可以使用另一个,只要它使用访问器而不是直接使用inst vars。

然后在控制台中你可以做(​​看到“重度计算”只打印一次,第一次调用export):

2.2.3 :001 > p = Product.new('X', 100)
 => #<Product:0x0000000104e128 @name="X", @net_price=100, @gross_price=nil> 
2.2.3 :002 > p.export
heavy calculation
 => {"name"=>"X", "net_price"=>100, "gross_price"=>1000} 
2.2.3 :003 > p.export
 => {"name"=>"X", "net_price"=>100, "gross_price"=>1000} 
2.2.3 :004 > 

这样做的一个优点是除了export之外需要gross_price的任何其他方法也可以获得它,并且export已经具有所有好处并且计算只需要执行一次

答案 2 :(得分:0)

  

我正努力成为更好的开发者......

你走在正确的轨道上。

您已检测到SRP的气味。您的Product至少做了两件事:

  1. 作为名称和价格的产品
  2. 导出
  3. 计算总价
  4. 我认为如果你创造1或2个课程,气味就会消失。

    您可能需要阅读POODR。并编写规范。