如何在Ruby中重构单例方法?

时间:2010-01-11 09:33:11

标签: ruby refactoring

目前我的代码如下(有些简化)。最后,我添加了越来越多的新类,如D1 / D2,我认为是时候进行一些重构以使其更优雅。当然,目标是将新的Dx类添加为尽可能少的重复代码。至少,在单例方法FileImporter.import中调用Dx.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 方式是什么?

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

最初,我遗漏了我认为不重要的代码以避免混淆。我应该提到上面的代码也是重构类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

3 个答案:

答案 0 :(得分: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>

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

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