【问题标题】:Clean object oriented code干净的面向对象代码
【发布时间】:2016-07-16 15:57:23
【问题描述】:

我正在努力成为一个更好的开发人员,我总是问自己是否有更好的方法来做这些事情。这不是我第一次处理这个问题,所以我决定问问你是怎么想的。

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

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

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

谢谢

【问题讨论】:

  • 嗯,这个问题可能更适合code review
  • 我认为您混合了两个问题(一个包含产品数据的对象以及您如何计算价格)
  • 你能详细解释一下吗@pascalbetz

标签: ruby oop coding-style


【解决方案1】:

我看到了几种方法:

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

愚蠢的对象

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

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

【讨论】:

    【解决方案2】:

    完全同意,如果只需要导出繁重的计算,那么您不应该在初始化时进行(具体情况,考虑编写单元测试;为什么所有测试都要付出沉重的代价)单个方法何时需要计算?)。

    避免@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 变量。

    然后在控制台中你可以做(​​看到“重计算”只打印一次,第一次调用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 已经拥有所有好处,并且只需要进行一次计算。

    【讨论】:

      【解决方案3】:

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

      你在正确的轨道上。

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

      1. 做一个有名字有价格的产品
      2. 导出
      3. 计算总价

      我认为,如果您再创建 1 或 2 个类,气味就会消失。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-13
        • 2011-02-15
        • 1970-01-01
        相关资源
        最近更新 更多