Официальный 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