【问题标题】:How to refactor singleton methods in Ruby?如何在 Ruby 中重构单例方法?
【发布时间】:2010-01-26 15:09:57
【问题描述】:

目前我有如下代码(有些简化)。最终,我添加了越来越多的新类,例如 D1/D2,我认为是时候进行一些重构以使其更优雅了。目标当然是使添加新类 Dx 使用尽可能少的重复代码。至少,应该排除在单例方法Dx.import 中调用FileImporter.import 的重复部分。

module FileImporter
  def self.import(main_window, viewers)
    ...
    importer = yield file  # delegate to D1/D2 for preparing the importer object
    ...
  end
end

class D1
  def self.import(main_window)
    viewers = [:V11, ]  # D1 specific viewers
    FileImporter.import(main_window, viewers) do |file|
      importer = self.new(file)
      ...  # D1 specific handling of importer
      return importer
    end
  end
end

class D2
  def self.import(main_window)
    viewers = [:V21,:v22, ]  # D2 specific viewers
    FileImporter.import(main_window, viewers) do |file|
      importer = self.new(file)
      ...  # D2 specific handling of importer
      return importer
    end
  end
end

# client code calls D1.import(...) or D2.import(...)

基本上FileImporter.import 是通用部分,Dx.import 是变体。我不确定如何重构这些单例方法。这样做的常见 Ruby 方法是什么?

更新:(上面的代码中添加了一些 cmets,希望能让我的意图更清晰......)

最初,为了避免混淆,我省略了我认为不重要的代码。我应该提到上面的代码也是重构类 D1 和 D2 的结果(通过将公共部分移出并移入模块 FileImporter)。 D1.importD2.import 的目的主要是创建适当类的对象(并且可能在从块返回之前进行一些特定于类的处理)。 FileImporter.import 主要是通用逻辑,在其中某些时候会屈服于特定的类来生成导入器对象。

我觉得 D1 和 D2 类看起来非常相似,应该可以进一步重构它们。例如,它们都调用FileImporter.import 来提供一个块,在其中都创建一个自己的对象。

解决方案:最初我没有意识到您可以通过在派生类的相应单例方法中调用super 来调用基类的单例方法。这确实是我遇到的主要问题,并且无法采用那条路线。所以我接受了@makevoid 答案,因为它确实使创建新的派生类更容易。

使用公共基类是一种优雅的重构解决方案,但其中一个问题是所有新的派生类都已经用完一个基类配额。我来到了这个类宏方法,它从派生类的角度提供了更简洁的结果。

module FileImporter
  def self.included(mod)
    mod.extend ClassMethods
  end

  module ClassMethods
    def importer_viewer(*viewers, &blk)
      @viewers = viewers
      @blk = blk

      class << self
        def import(main_window)
          if @blk.nil?
            FileImporter.import(main_window, @viewers) do |file|
              self.new(file)
            end
          else
            FileImporter.import(main_window, @viewers, &@blk)
          end      
        end
      end
    end
  end

  def self.import(main_window, viewers, multi=true)
    ...
    importer = yield file  # delegate to D1/D2 for preparing the importer object
    ...
  end
end

class D1
  include FileImporter
  importer_viewer [:V11, ] do
    ...  # D1 specific handling of importer
  end
end

class D2
  include FileImporter
  importer_viewer [:V21,:v22, ] do
    ...  # D2 specific handling of importer
  end
end

【问题讨论】:

  • 如果实际目的仍然是个谜并且没有给出所有代码,那么重构代码就很棘手。你能分享更多细节吗?另外,我真的希望您的课程实际上不称为 D1 和 D2。 :-)
  • 刚刚添加了更多细节。是的,D1 和 D2 是为此目的而编造的名称。 :-)

标签: ruby refactoring


【解决方案1】:

也许这不是最好的解决方案,但起初似乎 Dx 类具有相同的行为,因此使用具有 self.import 方法的 C 类对它们进行子类化,该方法使用块来接受一些其他代码。或者也可以通过包含一个模块来完成。

无论如何,这样的事情应该可以工作(对不起,较短的名称,但有利于原型设计)。 另请注意,我将 FileImporter.import 方法名称更改为另一个以避免误解 并注意我没有测试过代码:)

module F
  def self.fimport(something)
    yield "file"
  end
end


class C   
  include F 

  def initialize(f)

  end

  def self.import(something, &block)
    F.fimport(something) { |f|
      d = self.new(f)
      block.call
      d
    }
  end
end


class D1 < C
  def self.import(something)
    super(something){
      puts something
    }
  end
end


class D2 < C
  def self.import(something)
    super(something){
      puts something
    }
  end
end

p D1.import("a")
p D2.import("b")

#=> a
#=> #<D1:0x100163068>
#=> b
#=> #<D2:0x100162e88>

【讨论】:

  • 这是一个很好的解决方案。但是,我感觉它更像是一种“Java”方式,即类型层次结构与块/闭包相结合。我想知道 Ruby 人做事的方式是否不同。
【解决方案2】:

似乎制作一个模块将是一个优雅的解决方案。但是,很难对代码的用途有一个模糊的概念。示例:

module Importer
  def import
    self.whatever # self should be D1 or D2 as the case may be
    # ...
  end
end

class D1
  include Importer
end

class D2
  include Importer
end

【讨论】:

  • 重构目标是单例方法。
  • 你的意思是类方法吗?这种技术也适用于这些人。 (使用extend 而不是include。)
  • D1.importD2.import 存在差异。使用这种模块扩展方法,它们的变体将如何以及放在哪里?
  • 嗯,解决问题的方法实际上取决于您需要做什么。从示例中,D1.importD2.import 看起来没有任何变化。似乎需要更多的上下文/解释才能真正找到有意义的解决方案。
【解决方案3】:

鉴于给出的代码和上下文有限,我怀疑类似以下的内容对您有用。如果不出意外,您可以了解如何使用模块来突破常见功能。

module FileImporter
  def self.internal_import(main_window, viewers)
    ...
    importer = yield file
    ...
  end
  private :self.internal_import
end

class D1
    include FileImporter
    def self.import(main_window)
      self.internal_import(main_window, [:V1, ])
    end
end

class D2
    include FileImporter
    def self.import(main_window)
      self.internal_import(main_window, [:V2, ])
    end
end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 2011-04-24
    • 2013-12-08
    相关资源
    最近更新 更多