代码之家  ›  专栏  ›  技术社区  ›  bryantsai

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

  •  2
  • bryantsai  · 技术社区  · 15 年前

    目前我有如下代码(稍微简化了一些)。最后,我添加了越来越多的新类,如d1/d2,我认为是时候进行一些重构,使其更加优雅了。当然,目标是尽可能少地使用重复的代码来添加新的类dx。至少,调用的重复部分 FileImporter.import 在singleton方法中 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(...)
    

    基本上 文件导入程序.import 是共同的部分,与 DX.导入 是变化。我不知道如何重构这些单例方法。常见的是什么 红宝石 这样做的方式?

    更新 :(上面的代码中添加了一些注释,希望能让我的意思更清楚…)

    最初,为了避免混淆,我省略了我认为不重要的代码。我应该提到上面的代码也是重构类d1和d2(通过将公共部分移出并移入模块)的结果 FileImporter )目的 D1.import D2.import 主要是为了创建适当类的对象(在从块返回之前,可能还需要一些特定于类的处理)。 文件导入程序.import 主要是公共逻辑,在这个逻辑中,在某一点上会产生特定的类来生成导入器对象。

    我觉得类d1和d2非常相似,应该可以进一步重构它们。例如,它们都调用 文件导入程序.import 提供一个块,在这个块中,两个块都创建一个自身的对象。

    解决方案 :最初我没有意识到您可以通过调用 super 从派生类的相应单例方法中。这真的是我遇到的主要问题,不能走那条路。所以我接受了@makevoid answer,因为它确实使创建新的派生类更加容易。

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

    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 回复  |  直到 15 年前
        1
  •  1
  •   makevoid    15 年前

    也许这不是最好的解决方案,但起初看起来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>
    
        2
  •  1
  •   Benjamin Oakes    15 年前

    似乎制作一个模块是一个很好的解决方案。然而,很难用一个模糊的概念来解释代码的用途。例子:

    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
    
        3
  •  0
  •   bta    15 年前

    考虑到给定的有限代码和上下文,我怀疑下面这样的内容对您有用。如果没有其他东西,您可以了解如何使用模块来突破通用功能。

    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