Как выбрать версию модуля для динамического включения в Ruby?
Я пишу небольшое приложение командной строки Ruby, которое использует fileutils
из стандартной библиотеки для файловых операций. В зависимости от того, как пользователь вызывает приложение, я хочу включить либо FileUtils
, FileUtils::DryRun
или же FileUtils::Verbose
,
поскольку include
является частным, однако, я не могу поместить логику выбора в объект initialize
метод. (Это была моя первая мысль, с тех пор я мог просто передать информацию о выборе пользователя в качестве параметра new
Я предложил два варианта, которые, кажется, работают, но я тоже не доволен:
Задайте глобальную переменную в пространстве имен приложения на основе выбора пользователя, а затем выполните условное включение в класс:
class Worker case App::OPTION when "dry-run" include FileUtils::DryRun etc.
Создайте подклассы, где единственная разница в том, какая версия
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 нет, так сказать, настоящей "приватности", но некоторые методы помечаются как частные не без причины.