Одна из моих любимых вещей в Ruby — это то, что это очень чистый объектно-ориентированный язык и все является объектом. Этот факт в сочетании с тем фактом, что Ruby также является языком времени выполнения, может позволить вам сделать довольно интересные изменения в стандартной библиотеке в вашей кодовой базе.
В этой статье я собираюсь кратко обсудить раздел метапрограммирования Ruby, известный как динамические методы. Это методы, которые вы пишете для существующих классов Ruby, которые уже находятся в кодовой базе стандартной библиотеки.
В этом руководстве мы работаем на основе того, что на вашем компьютере установлен Ruby.
От IRB
Самое быстрое место, где можно узнать о любой новой концепции в Ruby, — это ввести ее в терминал IRB. В окне терминала введите следующее:
$ irb
Теперь вы должны быть в терминале IRB. Далее мы собираемся добавить метод к классу строк с именем William. Этот метод просто возвращает мое имя, но он должен дать вам представление о том, что такое динамический метод.
class String def william return "William" end end
Теперь в терминале IRB мы можем сделать следующее:
irb> "string".william => "William"
Из этого примера видно, что динамический метод — это метод, который создается во время выполнения. Вы можете добавить другой метод в класс в тот момент, когда код будет выполнен. Динамические методы открывают совершенно новую парадигму, когда речь идет о решении общих задач кодирования. Давайте посмотрим, как мы можем использовать динамические методы для создания программы, которая преобразует небольшие числа в римские цифры.
Решение проблемы римских цифр с использованием динамических методов
Программирование — это смесь искусства и науки. К сожалению, когда дело доходит до проблем кодирования, есть много правильных ответов, которые вызывают ожесточенные дебаты. Общая проблема, которую я вижу опубликованной, приходит в форме римских цифр.
Как бы мы написали компьютерную программу, которая преобразует число в римскую цифру, используя динамические методы?
Давайте посмотрим на одно возможное решение, которое достигает 5000. Из окна терминала, давайте создадим новый проект
mkdir roman_numeral
Теперь мы можем создать новый файл:
touch roman_numerals.rb
Мне нравится писать тесты для моего кода, поэтому мы установим RSpec и создадим спецификацию:
gem install rspec
После того, как мы установили гем RSpec, мы можем инициализировать Rspec
rspec --init
В нашем каталоге спецификаций мы можем создать спецификацию римской цифры.
touch spec/roman_numeral_spec.rb
Теперь у нас есть все готовое для начала проекта. Давайте начнем с написания множества тестов. Добавьте следующее в наш spec / roman_numeral_spec.rb :
require_relative "../roman_numerals.rb" RSpec.describe Numeric, "#roman_numeral" do context "#convert number into roman numeral" do it "1.roman_numeral returns I" do expect(1.roman_numeral).to eq "I" end it "5.roman_numeral returns I" do expect(5.roman_numeral).to eq "V" end it "4.roman_numeral returns IV" do expect(4.roman_numeral).to eq "IV" end it "6.roman_numeral returns IV" do expect(6.roman_numeral).to eq "VI" end it "7.roman_numeral returns VII" do expect(7.roman_numeral).to eq "VII" end it "8.roman_numeral returns VII" do expect(8.roman_numeral).to eq "VIII" end it "9.roman_numeral returns IX" do expect(9.roman_numeral).to eq "IX" end it "10.roman_numeral returns IX" do expect(10.roman_numeral).to eq "X" end it "13.roman_numeral returns XIII" do expect(13.roman_numeral).to eq "XIII" end it "15.roman_numeral returns XV" do expect(15.roman_numeral).to eq "XV" end it "18.roman_numeral returns XVIII" do expect(18.roman_numeral).to eq "XVIII" end it "19.roman_numeral returns XIX" do expect(19.roman_numeral).to eq "XIX" end it "30.roman_numeral returns XXX" do expect(30.roman_numeral).to eq "XXX" end it "50.roman_numeral returns L" do expect(50.roman_numeral).to eq "L" end it "51.roman_numeral returns LI" do expect(51.roman_numeral).to eq "LI" end it "89.roman_numeral returns LXXXIX" do expect(89.roman_numeral).to eq "LXXXIX" end it "99.roman_numeral returns XCIX" do expect(99.roman_numeral).to eq "XCIX" end it "145.roman_numeral returns CXLV" do expect(145.roman_numeral).to eq "CXLV" end it "459.roman_numeral returns CDLIX" do expect(459.roman_numeral).to eq "CDLIX" end it "1984.roman_numeral returns MCMLXXXIV" do expect(1984.roman_numeral).to eq "MCMLXXXIV" end it "1545.roman_numeral returns MDXLV" do expect(1545.roman_numeral).to eq "MDXLV" end it "4936.roman_numeral returns MMMMCMXXXVI" do expect(4936.roman_numeral).to eq "MMMMCMXXXVI" end end end
Теперь, когда у нас есть несколько написанных тестов, давайте Fixnum
в использование динамических методов класса Fixnum
, которые преобразуют наши числа в римские цифры. Как видно из наших тестов, у нас есть следующий пример:
it "1984.roman_numeral returns MCMLXXXIV" do expect(1984.roman_numeral).to eq "MCMLXXXIV" end
Динамические методы позволяют нам создавать метод, который мы можем вызывать в экземплярах класса Fixnum
. В Ruby есть полезные методы, такие как to_s
который преобразует число в строку. В этом случае мы вдохновляемся соглашениями Ruby и добавляем метод без изменения какой-либо из библиотек Ruby. Мы просто добавляем новый метод. Посмотрим, как это делается.
Первое, что мы делаем, это определяем класс Fixnum. Даже если уже существует класс Ruby с именем Fixnum, выполнение следующих действий не приведет к перезаписи реального класса. В нашем файле roman_numerals.rb :
class Fixnum end
Теперь мы можем изменить класс Fixnum
, добавив дополнительный метод. Чтобы наши тесты прошли to_roman_numeral
, нам нужен метод to_roman_numeral
который мы можем вызывать для целых чисел. Напишите следующее в нашем файле roman_numerals.rb :
class Fixnum def roman_numeral return "The Romans have no zeros just heros. https://www.theguardian.com/notesandqueries/query/0,5753,-1358,00.html " if self == 0 symbols = {1000 => "M",900 => "CM", 500 => "D",400 => "CD", 100 => "C",90 => "XC", 50 => "L",40 =>"XL", 10 => "X",9 => "IX", 5 => "V",4 => "IV", 1=> "I"} multiplier = self symbol = [] count = 0 symbols.each do |num, sym| symbol.push(sym * (multiplier/num)) multiplier = multiplier % num count += 1 end return symbol.join end end
Когда вы запустите RSpec, вы должны увидеть, что все тесты пройдены. Из текущего каталога запустите следующее:
rspec
Теперь вы видели динамические методы в действии. Теперь мы можем вызвать .to_roman_numeral
на любой номер, как мы .to_s
. Динамические методы отлично подходят для небольших программ, подобных этой, но имейте в виду, что добавление динамических методов в ваше приложение может привести к серьезным недостаткам, если вы не будете осторожны.
ВНИМАНИЕ: перезапись существующих методов
Самым большим потенциальным недостатком динамических методов является перезапись существующих методов в экосистеме вашими собственными методами. Это может привести к взлому Gems, молчаливым ошибкам или даже слишком сложному коду.
Давайте снова возьмем класс Fixnum
. Если мы случайно напишем наш собственный метод .to_s
, то мы потенциально удалили ядро, ожидаемую функциональность из нашего приложения. Поскольку код будет выполняться во время выполнения, мы не увидим, пока ошибка не будет вызвана, и даже в этом случае она может привести к тихой ошибке.
Когда дело доходит до динамических методов, это платит, чтобы быть аддитивным. Вместо перезаписи методов безопаснее добавлять и быстро проверять, не перезаписывает ли существующий метод. В связи с этим я настоятельно рекомендую тесты, чтобы охватить ваше приложение, чтобы вы могли быстро определить, когда что-то сломалось.
Вывод
Теперь, когда вы быстро познакомились с аспектом Ruby MetaProgramming, как бы вы его использовали? Как вы думаете, динамические методы полезны или они слишком опасны для собственного блага? Я хотел бы услышать ваше мнение в комментариях ниже.