Зміст | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

Офіційний FAQ Ruby

If you wish to report errors or suggest improvements for this FAQ, please go to our GitHub repository and open an issue or pull request.

Методи

Як Ruby вибирає, який метод викликати?

Ruby динамічно прив’язує всі повідомлення до методів. Спершу він шукає singleton-методи в отримувачі, потім методи, визначені у власному класі отримувача, і нарешті методи, визначені в суперкласах отримувача (включно з будь-якими модулями, які могли бути домішані). Порядок пошуку можна побачити, вивівши ClassName.ancestors, який показує класи-предки та модулі ClassName.

Якщо після пошуку серед альтернатив відповідний метод не знайдено, Ruby намагається викликати метод method_missing, повторюючи ту саму процедуру пошуку для нього. Це дає змогу обробляти повідомлення до невідомих методів і часто використовується для надання динамічних інтерфейсів класам.

module Emphasizable
  def emphasize
    "**#{self}**"
  end
end

class String
  include Emphasizable
end

String.ancestors
  # => [String, Emphasizable, Comparable, Object, Kernel, BasicObject]

"Wow!".emphasize  # => "**Wow!**"

Коли під час пошуку методу emphasize його не знаходять у класі String, Ruby далі шукає в модулі Emphasizable.

Щоб перевизначити метод, який уже існує в класі отримувача, наприклад String#capitalize, потрібно вставити модуль у ланцюжок предків перед цим класом, використавши prepend:

module PrettyCapitalize
  def capitalize
    "**#{super}**"
  end
end

class String
  prepend PrettyCapitalize
end

String.ancestors
  # => [PrettyCapitalize, String, Comparable, Object, Kernel, BasicObject]

"hello".capitalize  # => "**Hello**"

Чи є +, -, *, … операторами?

+, - та подібні — це не оператори, а виклики методів. Тому їх можна перевантажувати новими визначеннями.

class MyString < String
  def -(other)
    self[0...other.size]  # self truncated to other's size
  end
end

Однак наведені нижче — вбудовані керувальні структури, а не методи, тому їх не можна перевизначити:

=, .., ..., not, ||, &&, and, or, ::

Щоб перевантажити або визначити унарні оператори + і -, потрібно використовувати +@ і -@ як назви методів.

Оператор = використовується для визначення методу, що встановлює атрибут об’єкта:

class Test
  def attribute=(val)
    @attribute = val
  end
end

t = Test.new
t.attribute = 1

Якщо визначені оператори на кшталт + і -, Ruby автоматично обробляє форми самоприсвоєння (+=, -= тощо).

Де ++ і --?

У Ruby немає операторів автоінкременту та автодекременту. Замість них можна використати += 1 і -= 1.

Що таке singleton-метод?

Singleton-метод — це метод екземпляра, пов’язаний з одним конкретним об’єктом.

Singleton-метод створюють, включаючи об’єкт у визначення:

class Foo; end

foo = Foo.new
bar = Foo.new

def foo.hello
  puts "Hello"
end

foo.hello
bar.hello

Виведе:

Hello
prog.rb:11:in `<main>': undefined method `hello' for #<Foo:0x000000010f5a40> (NoMethodError)

Singleton-методи корисні, коли потрібно додати метод до об’єкта, а створення нового підкласу недоцільне.

Усі ці об’єкти — добре, але чи має Ruby прості функції?

І так, і ні. У Ruby є методи, які виглядають як функції в мовах на кшталт C або Perl:

def hello(name)
  puts "Hello, #{name}!"
end

hello("World")

Виведе:

Hello, World!

Однак насправді це виклики методів без явного отримувача. У цьому випадку Ruby вважає, що отримувач — це self.

Отже, hello схожа на функцію, але насправді це метод, що належить класу Object, і надсилається як повідомлення прихованому отримувачу self. Ruby — чисто об’єктно-орієнтована мова.

Звісно, ви можете використовувати такі методи так, ніби це функції.

Звідки беруться всі ці методи, схожі на функції?

Майже всі класи в Ruby походять від класу Object. Визначення класу Object домішує методи, визначені в модулі Kernel. Тому ці методи доступні в кожному об’єкті системи.

Навіть якщо ви пишете просту програму Ruby без класів, ви фактично працюєте всередині класу Object.

Чи можу я отримати доступ до змінних екземпляра об’єкта?

Змінні екземпляра об’єкта (ті, що починаються з @) безпосередньо недоступні поза об’єктом. Це забезпечує хорошу інкапсуляцію. Проте Ruby спрощує визначення методів доступу до цих змінних екземпляра так, щоб користувачі вашого класу могли поводитися з ними як з атрибутами. Просто використайте один або кілька з attr_reader, attr_writer чи attr_accessor.

class Person
  attr_reader   :name           # read only
  attr_accessor :wearing_a_hat  # read/write

  def initialize(name)
    @name = name
  end
end

p = Person.new("Dave")
p.name           # => "Dave"
p.wearing_a_hat  # => nil
p.wearing_a_hat = true
p.wearing_a_hat  # => true

Ви також можете визначити власні методи доступу (наприклад, для валідації або обробки похідних атрибутів). Метод читання — це просто метод без параметрів, а метод присвоєння — це метод із назвою, що закінчується на =, і який приймає один параметр. Хоча у визначенні методу між назвою методу та = не може бути пробілу, під час виклику можна вставляти пробіли, і це виглядатиме як звичайне присвоєння. Також можна використовувати самоприсвоєння на кшталт += і -=, якщо визначені відповідні методи + або -.

У чому різниця між private і protected?

Ключове слово видимості private робить метод викличним лише у функціональній формі, без явного отримувача, і тому його отримувачем може бути лише self. Приватний метод можна викликати лише в межах класу, в якому він визначений, або в його підкласах.

class Test
  def foo
    99
  end

  def test(other)
    p foo
    p other.foo
  end
end

t1 = Test.new
t2 = Test.new

t1.test(t2)

# Now make `foo' private

class Test
  private :foo
end

t1.test(t2)

Виведе:

99
99
99
prog.rb:8:in `test': private method `foo' called for #<Test:0x00000000b57a48> (NoMethodError)
        from prog.rb:23:in `<main>'

Захищені методи також можна викликати лише всередині свого класу або його підкласів, але їх можна викликати як у функціональній формі, так і з отримувачем. Наприклад:

def <=>(other)
  age <=> other.age
end

Код скомпілюється, якщо age — захищений метод, але не якщо він приватний.

Ці можливості допомагають контролювати доступ до внутрішньої реалізації вашого класу.

Як змінити видимість методу?

Видимість методів змінюють за допомогою private, protected і public. Якщо використовувати їх без параметрів під час визначення класу, вони впливають на видимість наступних методів. Якщо використовувати з параметрами, вони змінюють видимість названих методів.

class Foo
  def test
    puts "hello"
  end
  private :test
end

foo = Foo.new
foo.test

Виведе:

prog.rb:9:in `<main>': private method `test' called for #<Foo:0x0000000284dda0> (NoMethodError)

Зробити метод класу приватним можна за допомогою private_class_method.

class Foo
  def self.test
    puts "hello"
  end
  private_class_method :test
end

Foo.test

Виведе:

prog.rb:8:in `<main>': private method `test' called for Foo:Class (NoMethodError)

Видимість за замовчуванням для методів, визначених у класі, — public. Виняток — метод ініціалізації екземпляра initialize.

Методи, визначені на верхньому рівні, також є public за замовчуванням.

Чи може ідентифікатор, що починається з великої літери, бути назвою методу?

Так, може, але робити так не варто без потреби! Якщо Ruby бачить назву з великої літери, за якою йде пробіл, він, ймовірно (залежно від контексту), вважатиме її константою, а не назвою методу. Тож якщо ви використовуєте імена методів з великої літери, завжди пам’ятайте ставити список параметрів у дужках і розміщувати дужки відразу після імені методу без пробілів. (Остання порада корисна в будь-якому разі!)

Виклик super спричиняє ArgumentError.

Виклик super без параметрів у методі передає всі аргументи цього методу методу з таким самим ім’ям у суперкласі. Якщо кількість аргументів у початкового методу не збігається з кількістю у методу вищого рівня, виникає ArgumentError. Щоб обійти це, просто викличте super і передайте потрібну кількість аргументів.

Як викликати метод з таким самим ім’ям на два рівні вище?

super викликає однойменний метод на один рівень вище. Якщо ви перевантажуєте метод у більш віддаленому предку, використайте alias, щоб надати йому нове ім’я до того, як замаскуєте його своїм визначенням методу. Потім можна викликати його за цим псевдонімом.

Як викликати оригінальний вбудований метод після його перевизначення?

Усередині визначення методу можна використати super. Також можна застосувати alias, щоб надати йому іншу назву. Нарешті, можна викликати оригінальний метод як singleton-метод Kernel.

Що таке деструктивний метод?

Деструктивний метод — це метод, що змінює стан об’єкта. String, Array, Hash та інші мають такі методи. Часто є дві версії методу: одна зі звичайною назвою, інша — з тією ж назвою, але з ! наприкінці. Звичайна версія створює копію отримувача, вносить до неї зміни й повертає копію. Версія з ! модифікує отримувача на місці.

Втім, слід пам’ятати, що є чимало деструктивних методів без !, зокрема методи присвоєння (name=), присвоєння елементів масиву ([]=) і методи на кшталт Array.delete.

Чому деструктивні методи можуть бути небезпечними?

Пам’ятайте, що присвоєння в більшості випадків лише копіює посилання на об’єкт, а передавання параметрів еквівалентне присвоєнню. Це означає, що ви можете отримати кілька змінних, які посилаються на той самий об’єкт. Якщо одна з них викликає деструктивний метод, зміниться об’єкт, на який посилаються всі.

def foo(str)
  str.sub!(/foo/, "baz")
end

obj = "foo"
foo(obj)     # => "baz"
obj          # => "baz"

У цьому випадку змінюється фактичний аргумент.

Чи можу я повертати з методу кілька значень?

Так і ні.

def m1
  return 1, 2, 3
end

def m2
  [1, 2, 3]
end

m1  # => [1, 2, 3]
m2  # => [1, 2, 3]

Отже, повертається лише одна річ, але нею може бути довільно складний об’єкт. У випадку масивів можна використати множинне присвоєння, щоб отримати ефект кількох повернених значень. Наприклад:

def foo
  [20, 4, 17]
end

a, b, c = foo
a              # => 20
b              # => 4
c              # => 17