Статьи

GSwR V: Методы к безумию

Мы рассмотрели некоторые из наиболее важных типов объектов Ruby в последних трех публикациях — строки, целые числа и числа с плавающей точкой, а также коллекции, такие как массивы, диапазоны и хеши. Мы также рассмотрели методы, которые придают этим объектам их функциональность. В этом посте мы рассмотрим создание наших собственных методов.

Чтобы определить метод, мы используем ключевое слово def Далее следует код для метода перед завершением с ключевым словом end Давайте посмотрим на некоторые примеры в IRB:

 def say_hello
  "Hello Ruby!"
end

Этот метод называется say_hello Последняя строка метода в Ruby — это возвращаемое значение, которое является значением, которое возвращается методом при вызове. Чтобы вызвать метод, просто введите его имя:

 say_hello
=> Hello Ruby!

Методы полезны. Они могут облегчить чтение вашего кода (если вы дадите описательные имена для ваших методов) и означают, что вам не нужно писать повторяющиеся блоки кода снова и снова. Кроме того, если вы хотите, чтобы некоторые функциональные возможности изменились, вам нужно обновить метод только в одном месте. Это известно как принцип СУХОЙ (не повторяй себя), и это важно помнить при программировании.

Добавление параметров

Методы можно сделать более эффективными, включив параметры . Это значения, которые предоставляют метод для использования. Например, мы можем улучшить метод say_hello

 def say_hello(name)
  "Hello #{name}!"
end

Параметры добавляются в определение метода после имени метода (круглые скобки необязательны, но рекомендуются). В теле функции имя параметра действует как переменная, равная значению, которое передается методу в качестве аргумента при его вызове:

 say_hello "Sinatra" # Again, parentheses are optional
=> "Hello Sinatra!"

В этом случае строка «Sinatra» предоставляется в качестве аргумента, и мы используем интерполяцию строки, чтобы вставить аргумент в строку, возвращаемую методом.

Мы можем предоставить значение по умолчанию для параметра, поместив его равным значению по умолчанию в определении метода:

 def say_hello(name="Ruby")
  "Hello #{name}"
end

Это означает, что теперь мы можем вызвать метод say_hello

 say_hello
=> "Hello Ruby!"

say_hello "DAZ"
=> "Hello DAZ!"

Мы можем добавить больше параметров, некоторые с параметрами по умолчанию, а некоторые без (но те, которые без, должны стоять на первом месте). Вот еще одна функция, называемая greet

 def greet(name,job,greeting="Hello")
  "#{greeting} #{name}, Your job as a #{job} sounds fun"
end

greet("Walt","Cook")
=> "Hello Walt, Your job as a Cook sounds fun"

greet("Jessie","Cook","Hey")
=> "Hey Jessie, Your job as a Cook sounds fun"

Мы также можем создавать методы с неопределенным числом аргументов, помещая ‘*’ перед последним параметром в определении метода. Любое количество аргументов будет затем сохранено в виде массива с тем же именем, что и параметр. Следующий метод примет любое количество имен в качестве аргумента и сохранит их в массиве с names

 def say_hello(*names)
  names.each { |name| puts "Hello #{name}!" }
end

say_hello("Walt","Skylar","Jessie")
Hello Walt!
Hello Skylar!
Hello Jessie!
 => ["Walt", "Sklar", "Jessie"]

say_hello "Sherlock", "John"
Hello Sherlock!
Hello John!
 => ["Sherlock", "John"]

say_hello "Heisenberg"
Hello Heisenberg!
 => ["Heisenberg"]

Обратите внимание, что возвращаемое значение метода — массив names

Другой вариант — вместо этого использовать ключевые аргументы (хотя они доступны только в Ruby версии 2.0 и выше). Они действуют с использованием синтаксиса, похожего на хеш, для параметров, как показано в обновленной версии нашего метода greet

 def greet(name="Ruby", job: "programming language", greeting: "Hello")
  "#{greeting} #{name}, Your job as a #{job} sounds fun"
end

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

 greet greeting: "Hi"
=> "Hi Ruby, Your job as a programming language sounds fun"

greet "Sherlock", job: "detective", greeting: "Greetings"
=> "Greetings Sherlock, Your job as a detective sounds fun"

greet "John", greeting: "Good Day", job: "doctor"
=> "Good Day John, Your job as a doctor sounds fun"

Обратите внимание, что порядок аргументов ключевого слова не имеет значения, и, если некоторые из них пропущены, вместо них используется значение по умолчанию.

Мы также можем добавить дополнительный параметр в конце с ** Это будет собирать любые дополнительные ключевые аргументы, которые не указаны в определении метода в хэше с тем же именем, что и параметр:

 def greet(name="Ruby", job: "programming language", greeting: "Hello", **options)
  "#{greeting} #{name}, Your job as a #{job} sounds fun. Here is some extra information about you: #{options}"
end

greet "Saul", job: "Colonel", human: false
=> "Hello Saul, Your job as a Colonel sounds fun. Here is some extra information about you: {:human=>false}"

greet "Kara", job: "Viper Pilot", callsign: "Starbuck", human: true
=> "Hello Kara, Your job as a Viper Pilot sounds fun. Here is some extra information about you: {:callsign=>\"Starbuck\", :human=>true}"

Также возможно добавить блок в качестве параметра в метод, поместив символ & Затем к блоку можно обратиться в определении метода, обратившись к нему. Это полезно, если вы хотите запустить какой-то определенный код при вызове метода. Вот базовый пример метода с именем repeat

 def repeat(number=2, &block)
  number.times { block.call }
end

repeat(3) { puts "Ruby!" }
Ruby!
Ruby!
Ruby!
 => 3

Блок является необязательным, и есть удобный метод с именем block_given? это позволяет нам проверить, задан ли блок при вызове метода. Вот метод roll_dice

 def roll_dice(sides=6,&block)
  if block_given?
    block.call(rand(1..sides))
  else
    rand(1..sides)
  end
end

Если метод вызывается без блока, он просто возвращает число, брошенное на кости:

 roll_dice
=> 6

Если мы хотим имитировать 20-стороннюю кость, мы можем ввести аргумент sides

 roll_dice(20)
=> 13

Теперь скажем, что мы хотим бросить кости, но затем удвоить результат и затем добавить 5. Мы можем использовать блок, чтобы сделать это:

 roll_dice { |result| 2 * result + 5 }

Другой пример использования блока может быть, если мы хотим вернуть, был ли результат броска костей нечетным или четным:

 roll_dice do |result|
  if result.odd?
    "odd"
  else
    "even"
  end
end

Использование блоков в качестве параметров может сделать методы чрезвычайно гибкими и мощными. Обработчики маршрута в Синатре принимают блоки в качестве аргументов. Определение метода для запроса GET

 def get(route,options={},&block)
  ... code goes here
end

Аргумент маршрута — это строка, которая сообщает нам маршрут для сопоставления. Затем следует хэш параметров, по умолчанию равный пустому хэшу. И наконец, метод принимает блок, который является кодом, который мы хотим запустить при посещении маршрута.

Рефакторинг Правильно разыграй свои карты

Мы собираемся провести рефакторинг кода для приложения Sinatra ‘Play Your Cards Right’, которое мы создали в последнем уроке. Рефакторинг кода — это процесс улучшения его структуры и удобства сопровождения без изменения его поведения.

То, что мы собираемся сделать, это заменить некоторые фрагменты кода методами. Это облегчит выполнение кода и его поддержку — если мы хотим внести изменения в функциональность, нам просто нужно изменить метод в одном месте.

Синатра использует вспомогательные методы для описания методов, которые используются в обработчиках маршрутов и представлениях. Они размещаются в блоке helpers

 helpers do

  # helper methods go here

end

Для начала, мы собираемся переписать приложение, используя имена методов для описания поведения, которое мы хотим. Создайте новый файл с именем ‘play_your_cards_right_refactored.rb’ и добавьте следующий код:

 require 'sinatra'
enable :sessions

configure do
  set :deck, []
  suits = %w[ Hearts Diamonds Clubs Spades ]
  values = %w[ Ace 2 3 4 5 6 7 8 9 10 Jack Queen King ]
  suits.each do |suit|
    values.each do |value|
    settings.deck << "#{value} of #{suit}"
    end
  end
end

helpers do
 # helper methods will go here
end

get '/' do
  set_up_game
  redirect to('/play')
end

get '/:guess' do
  card = draw_card
  value = value_of card

  if player_has_a_losing value
    game_over card
  else
    update_session_with value
    ask_about card
  end
end

Это очень похоже на последний кусок кода (и он имеет точно такую ​​же функциональность), за исключением того, что гораздо проще увидеть, что происходит в каждом обработчике маршрута. Это потому, что мы заменили большую часть кода Ruby методами и выбрали некоторые описательные имена методов, чтобы сделать код более читабельным. Это почти похоже на псевдокод .

Давайте посмотрим на каждый обработчик маршрута по очереди, чтобы увидеть, что он делает:

 get '/' do
  set_up_game
  redirect to('/play')
end

Этот обработчик маршрута настраивает игру, а затем перенаправляет на маршрут «/ play»… фактически, это даже не нужно объяснять, потому что он говорит это прямо в коде! Имена методов точно сообщают нам, что происходит — весь код был извлечен в метод с именем set_up_game Поместите следующее внутри блока помощников:

 def set_up_game
  session[:deck] = settings.deck.shuffle
  session[:guesses] = -1
  session[:value] = 0
end

Это устанавливает переменные сеанса, которые нам понадобятся в игре. Колода перетасовывается, догадки устанавливаются на -1 (потому что она увеличивается на 1 при первой игре, даже если правильное предположение не было сделано), а переменная значения устанавливается на 0.

redirectto

Теперь давайте посмотрим на начало обработчика маршрута, который имеет отношение к игровому процессу:

 get '/:guess' do
  card = draw_card
  value = value_of card

    # more code follows
end

Прежде всего нам нужно нарисовать карту, поэтому мы пишем метод для этого. Добавьте следующее в блок helpers

 def draw_card
  session[:deck].pop
end

Это очень короткий метод, но он дает нам более наглядный код.

Далее мы хотим узнать стоимость карты. Код, который мы использовали для этого в части 4, был довольно длинным case Мы можем извлечь это в метод, который принимает в качестве аргумента карту, а затем возвращает значение этой карты. Следующий код также входит в блок helpers

 def value_of card
  case card[0]
    when "J" then 11
    when "Q" then 12
    when "K" then 13
    else card.to_i
  end
end

Далее у нас есть оператор if

 if player_has_a_losing value
  game_over card
else
  update_session_with value
  ask_about card
end

Это также использует имя методов, чтобы сделать код очень описательным. Первый метод называется player_has_a_losingvalue Эта комбинация имени и имени параметра делает его хорошо читаемым. Этот метод также должен идти в блоке helpers

 def player_has_a_losing value
  (value < session[:value] and params[:guess] == 'higher') or (value > session[:value] and params[:guess] == 'lower')
end

Это возвращает truefalsesession[:value]params:guess

Если метод player_has_a_losingfalse Первый — update_session_withvalue Это происходит точно так же, как указано, а также обновляет число догадок на 1. Это также входит в блок helpers

 def update_session_with value
  session[:value] = value
  session[:guesses]  += 1
end

Следующий метод, который должен идти в блоке helpersask_about Это просто спрашивает игрока, является ли карта выше или ниже. Он принимает текущую карту в качестве аргумента:

 def ask_about card
  "The card is the #{ card }. Do you think the next card will be <a href='/higher'>Higher</a> or <a href='/lower'>Lower</a>?"
end

Это последний из всех наших вспомогательных методов. Если вы попытаетесь запустить код, введя ruby play_your_cards_right_refactored.rbhttp: // localhost: 4567, вы должны увидеть ту же игру, что и раньше. Это именно то, что мы хотим сделать, когда мы реорганизуем наш код — никаких изменений снаружи, но более читаемый и поддерживаемый код внутри.

Сфера

В некоторых вспомогательных методах, которые мы только что использовали, мы должны были указать карту или значение в качестве параметра методов. Вы можете спросить, почему мы должны были это делать, когда cardvalue Это связано с тем, что переменная существует внутри метода только в том случае, если она была создана в методе или если она была введена в качестве аргумента. Это можно увидеть в следующем примере (отметьте это в IRB):

 name = "Walt"
job = "teacher"

def say_my_name
  name = "Heisenberg"
  job = "cook"
  puts "You're #{name} and you're a #{job}"
end

puts "You're #{name} and you're a #{job}"
=> You're Walt and you're a teacher

say_my_name
=> You're Heisenberg and you're a Cook

Когда putsnamejobnamejob

У метода нет никакого доступа к любой переменной, созданной вне его (они должны быть введены как аргументы метода). Вы также не можете получить доступ к любой переменной, созданной внутри метода, вне метода — любые значения Вы хотите получить доступ после того, как метод был вызван, должен быть возвращен методом. Места в коде, где доступна переменная, известны как область действия переменной.

Это все люди

Надеемся, что это руководство помогло представить методы и показать, насколько они полезны для того, чтобы сделать ваш код более гибким, поддерживаемым, многократно используемым и более легким для чтения (при условии, что они хорошо названы).

Методы, которые мы писали в этом уроке, были больше похожи на функции . Как я упоминал в самом первом посте, Ruby на самом деле является объектно-ориентированным языком, а методы должны быть методами объектов. Методы, которые мы писали, на самом деле являются всеми методами специального mainЭтот пост Pat для более подробной информации об этом).

В следующем посте мы станем классными и посмотрим, как работает система классов Ruby. Мы рассмотрим, как добавлять методы к существующим классам, таким как Strings и Integers, и как создавать свои собственные классы с их собственными открытыми и закрытыми методами. А пока, пожалуйста, оставляйте любые комментарии или вопросы, которые могут у вас возникнуть в разделе комментариев ниже.