Оглавление | 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 связывает все сообщения с методами динамически. Сначала он ищет синглтон-методы у получателя, затем методы, определенные в собственном классе получателя, и, наконец, методы, определенные в суперклассах получателя (включая любые модули, которые могли быть подмешаны). Вы можете увидеть порядок поиска, выведя 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 method) — это метод экземпляра, связанный с одним конкретным объектом.

Вы создаете синглтон-метод, включая объект в определение:

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> (NameError)

Синглтон-методы полезны, когда вы хотите добавить метод к объекту, а создание нового подкласса нецелесообразно.

Все эти объекты — это прекрасно, но есть ли в 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 позволяет вам легко определять аксессоры (accessors) к этим переменным экземпляра таким образом, чтобы пользователи вашего класса могли обращаться с переменными экземпляра так же, как с атрибутами. Просто используйте один или несколько из 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>'

Защищенные (protected) методы также можно вызвать только из их собственного класса или его подклассов, но их можно вызывать как в форме функции, так и с использованием получателя. Например:

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.

Методы, определенные на верхнем уровне, также являются публичными по умолчанию.

Может ли идентификатор, начинающийся с заглавной буквы, быть именем метода?

Да, может, но мы не делаем этого просто так! Если Ruby видит имя с большой буквы, за которым следует пробел, он, вероятно (в зависимости от контекста), предположит, что это константа, а не имя метода. Поэтому, если вы используете имена методов с большой буквы, всегда помните о том, что списки параметров нужно заключать в скобки, и всегда ставьте скобки сразу после имени метода без пробелов. (Этот последний совет полезен в любом случае!)

Вызов super выдает ArgumentError.

Вызов super без параметров в методе передает все аргументы этого метода методу с тем же именем в суперклассе. Если количество аргументов в исходном методе не совпадает с количеством аргументов в методе более высокого уровня, возникает ошибка ArgumentError. Чтобы обойти это, просто вызовите super и передайте подходящее количество аргументов.

Как я могу вызвать метод с тем же именем на два уровня выше?

super вызывает одноименный метод на один уровень выше. Если вы перегружаете метод у более далекого предка, используйте alias, чтобы дать ему новое имя перед тем, как скрыть его своим определением метода. Затем вы сможете вызвать его по этому новому имени.

Как я могу вызвать оригинальный встроенный метод после его переопределения?

Внутри определения метода вы можете использовать super. Также вы можете использовать alias, чтобы дать ему альтернативное имя. Наконец, вы можете вызвать оригинальный метод как синглтон-метод 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