Управление версиями объекта Ruby

У меня есть ряд объектов Ruby, которые моделируют базовый XML (например, 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.

Идеи?


person John Straughn    schedule 05.07.2011    source источник


Ответы (3)


Я вижу несколько вариантов для вас.

Вы можете проксировать вызовы методов на XMLModel с помощью method_missing.

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
person Jeremy    schedule 05.07.2011
comment
Это будет работать при загрузке XML, но не будет работать при попытке создать XML. Вы думаете, что я должен использовать для этого и не-new фабричный метод (т.е. create)? - person John Straughn; 05.07.2011
comment
Разве метод to_xml не будет частью какого-либо из конкретных версионных классов? - person Jeremy; 05.07.2011
comment
Да, to_xml будет частью каждого версионного класса, но если пользователь хочет создать XML-документ с нуля, ему нужно получить новый (пустой) объект, который они затем могут «заполнить», вызвав методы установки, вроде как Рельс ActiveRecord. - person John Straughn; 05.07.2011
comment
Дальше все зависит от того, как они будут указывать версию при создании модели с нуля. Если вы не слишком заботитесь об отражении, то прокси-сервер method_missing может быть вашим лучшим выбором. - person Jeremy; 05.07.2011
comment
Я бы избегал модулей или method_missing, если это возможно, просто потому, что они менее понятны и их сложнее отлаживать, чем простое наследование на основе классов. - person Ian; 05.07.2011

Почему бы не создать подкласс, который наследуется от 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
person SvenK    schedule 05.07.2011
comment
Можете ли вы скрыть этот оператор if в инициализации XMLModel (т.е. new)... Я не могу без использования отдельной заводской инициализации, т.е. XMLModel.create. Я бы хотел, чтобы люди не знали об управлении версиями и могли использовать объект как обычный объект Ruby и вызывать XMLModel.new, тем самым скрывая всю реализацию за моделью. - person John Straughn; 05.07.2011
comment
вы можете переопределить ::new. Новый метод по умолчанию (я думаю) выглядит примерно так: def new(*args); obj = allocate; obj.initialize(*args); retrun obj; end Извините за однострочный текст, комментарии и код плохо сочетаются. - person Ian; 05.07.2011
comment
добавлен ответ с более подробной проработкой переопределения :: new - person Ian; 05.07.2011

Это не полный ответ, а просто улучшенная версия моего комментария, описывающая, как вы можете переопределить ::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
person Ian    schedule 05.07.2011