【问题标题】:Versioning a Ruby Object对 Ruby 对象进行版本控制
【发布时间】:2011-07-05 14:50:32
【问题描述】:

我有一系列为底层 XML 建模的 Ruby 对象(如 OXM)。不幸的是,XML 正在更改,相应的版本正在更新。我需要更新我的 Ruby 对象才能处理这两个版本。我想要比我的方法中的大量 if/else 子句更干净的东西,因为这很可能会再次发生。有没有一种惯用的 Ruby 方法来处理这个问题?我正在考虑使用基类作为各种“版本化”类的代理,即

class XMLModel
  class V1 
    # V1 specific implementation
  end

  class V2;
    # V2 specific implementation
  end

  def initialize
    # create a new V? and set up delegation to that specific version of the object
  end

  def from_xml(xml_string)
    # use the XML string to determine correct version and return a 
    # specific version of the object
  end
end

上述方法的好处是每个版本在代码中都是不同的,并且允许我添加/删除几乎没有向后兼容性测试的版本。坏事是我可能会得到很多代码重复。此外,在这种情况下,XMLModel.new 返回一个新的XMLModel,而XMLModel.from_xml 工厂方法返回一个新的XMLModel::V1

想法?

【问题讨论】:

    标签: ruby versioning idioms


    【解决方案1】:

    我看到了一些适合你的选择。

    您可以使用method_missing 代理对XMLModel 的方法调用。

    class XMLModel
    
      def load_xml(xml)
        version = determine_version(xml)
        case version
        when :v1
          @model = XMLModelV1.new
        when :v2
          @model = XMLModelV2.new
        end
      end
    
      def method_missing(sym, *args, &block)
        @model.send(sym, *args, &block)
      end
    
    end
    

    另一种选择是将方法从特定版本动态复制到XMLModel 的实例。但是,除非完全必要,否则我不鼓励这样做。

    第三个选项是为每个版本创建一个具有特定于该版本的方法的模块。然后,您将只包含特定版本的模块,而不是使用代理对象。

    module XMLModelV1
      #methods specific to v1
    end
    
    module XMLModelV2
      #methods specific to v2
    end
    
    class XMLModel
      #methods common to both versions
    
      def initialize(version)
        load_module(version)
      end
    
      def load_xml(xml)
        load_module(determine_version(xml))
      end
    
    private
    
      def load_module(version)
        case version
        when :v1
          include XMLMOdelV1
        when :v2
          include XMLModelV2
        end
      end
    end
    

    【讨论】:

    • 这在加载 XML 时有效,但在尝试创建 XML 时无效。您是否认为我应该为此也使用非new 工厂方法(即create)?
    • to_xml 方法不会是特定版本类的一部分吗?
    • 是的,to_xml 将是每个版本化类的一部分,但如果用户想从头开始创建 XML 文档,他们需要获取一个新的(空)对象,然后他们可以“填写”通过调用 setter 方法,类似于 Rails ActiveRecord。
    • 那么这取决于他们在从头开始创建模型时如何指定版本。如果您不太关心反射,那么method_missing 代理可能是您的最佳选择。
    • 如果可能,我会避免使用模块或method_missing,因为它们比简单的基于类的继承更清晰且更难调试。
    【解决方案2】:

    为什么不构建继承自 XMLModel 的子类,那么类之间的决定只是代码中的一个点。

    class XMLModel_V1 < XMLModel  
        def from_xml(xml_string)
            # do V1 specific things
        end
    end
    
    class XMLModel_V2 < XMLModel
        def from_xml(xml_string)
            # do V2 specific things
        end
    end
    
    
    # Sample code wich shows the usage of the classes
    if(V1Needed)
        m = XMLModel_V1
    else
        m = XMLModel_V2
    end
    

    【讨论】:

    • 你能在 XMLModel 初始化中隐藏if 语句吗(即new)...我似乎无法不使用单独的工厂初始化,即XMLModel.create。我希望人们不必了解版本控制,并且能够将对象用作普通 Ruby 对象并调用XMLModel.new,从而将所有实现隐藏在模型后面。
    • 您可以覆盖::new。默认的新方法是(我认为)类似:def new(*args); obj = allocate; obj.initialize(*args); retrun obj; end 对不起,单行,cmets 和代码混合不好。
    • 添加了一个更详细地重新定义 ::new 的答案
    【解决方案3】:

    这不是一个完整的答案,而只是我评论的格式更好的版本,描述了如何覆盖 ::new。

    class XMLModel
      def new *args
        if self == XMLModel
          klass = use_v1? ? XMLModelV1 : XMLModelV2
          instance = klass.allocate
          instance.initialize *args
          instance         
        else
          super *args
        end
      end
    end
    # and of course:
    class XMLModelV1 < XMLModel; end
    class XMLModelV2 < XMLModel; end
    

    【讨论】:

      猜你喜欢
      • 2017-03-22
      • 1970-01-01
      • 2011-01-22
      • 1970-01-01
      • 2011-10-13
      • 2018-05-31
      • 1970-01-01
      • 2023-02-11
      • 2011-12-09
      相关资源
      最近更新 更多