Как выбрать версию модуля для динамического включения в Ruby?

Я пишу небольшое приложение командной строки Ruby, которое использует fileutils из стандартной библиотеки для файловых операций. В зависимости от того, как пользователь вызывает приложение, я хочу включить либо FileUtils, FileUtils::DryRun или же FileUtils::Verbose,

поскольку include является частным, однако, я не могу поместить логику выбора в объект initialize метод. (Это была моя первая мысль, с тех пор я мог просто передать информацию о выборе пользователя в качестве параметра newЯ предложил два варианта, которые, кажется, работают, но я тоже не доволен:

  1. Задайте глобальную переменную в пространстве имен приложения на основе выбора пользователя, а затем выполните условное включение в класс:

    class Worker
      case App::OPTION
      when "dry-run"
        include FileUtils::DryRun
        etc.
    
  2. Создайте подклассы, где единственная разница в том, какая версия FileUtils они включают. Выберите подходящий, в зависимости от выбора пользователя.

    class Worker
      include FileUtils
      # shared Worker methods go here
    end
    class Worker::DryRun < Worker
      include FileUtils::DryRun
    end
    class Worker::Verbose < Worker
      include FileUtils::Verbose
    end
    

Первый метод кажется СУХИМ, но я надеюсь, что есть что-то более простое, о чем я не думал.

3 ответа

Решение

Так что, если это личное?

class Worker
  def initialize(verbose=false)
    if verbose
      (class <<self; include FileUtils::Verbose; end)
    else
      (class <<self; include FileUtils; end)
    end
    touch "test"
  end
end

Это включает FileUtils::something в частности Workerметакласс - не в основной Worker учебный класс. Разные работники могут использовать разные FileUtils сюда.

У меня условно включение модуля через методы отправки работает как в тестируемом примере ниже:

class Artefact
  include HPALMGenericApi
  # the initializer just sets the server name we will be using ans also the 'transport' method : Rest or OTA (set in the opt parameter)
  def initialize server, opt = {}  
    # conditionally include the Rest or OTA module
    self.class.send(:include, HPALMApiRest) if (opt.empty? || (opt && opt[:using] opt[:using] == :Rest)) 
    self.class.send(:include, HPALMApiOTA) if (opt && opt[:using] opt[:using] == :OTA)    
    # ... rest of initialization code  
  end
end

Если вы хотите избежать "переключения" и ввести модуль,

def initialize(injected_module)
    class << self
        include injected_module
    end
end

синтаксис не будет работать (переменная injected_module находится вне области видимости). Вы могли бы использовать трюк self.class.send, но расширение экземпляра объекта кажется мне более разумным, не только потому, что оно короче:

def initialize(injected_module = MyDefaultModule)
    extend injected_module
end

но он также сводит к минимуму побочные эффекты - общее и легко изменяемое состояние класса, которое может привести к неожиданному поведению в более крупном проекте. В Ruby нет, так сказать, настоящей "приватности", но некоторые методы помечаются как частные не без причины.

Другие вопросы по тегам