Официальный 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.
Классы и модули
Может ли определение класса повторяться?
Класс можно определять многократно. Каждое определение добавляется к предыдущему. Если метод переопределяется, старый метод замещается и теряется.
Существуют ли переменные класса?
Да. Переменная с префиксом из двух символов «собака» (@@) — это переменная класса, доступная как в методах экземпляра, так и в методах класса.
class Entity
@@instances = 0
def initialize
@@instances += 1
@number = @@instances
end
def who_am_i
"I'm #{@number} of #{@@instances}"
end
def self.total
@@instances
end
end
entities = Array.new(9) { Entity.new }
entities[6].who_am_i # => "I'm 7 of 9"
Entity.total # => 9
Однако вам, вероятно, стоит использовать переменные экземпляра класса (class instance variables) вместо них.
Что такое переменная экземпляра класса?
Вот пример из предыдущего раздела, переписанный с использованием переменной экземпляра класса:
class Entity
@instances = 0
class << self
attr_accessor :instances # provide class methods for reading/writing
end
def initialize
self.class.instances += 1
@number = self.class.instances
end
def who_am_i
"I'm #{@number} of #{self.class.instances}"
end
def self.total
@instances
end
end
entities = Array.new(9) { Entity.new }
entities[6].who_am_i # => "I'm 7 of 9"
Entity.instances # => 9
Entity.total # => 9
Здесь @instances — это переменная экземпляра класса. Она принадлежит не экземпляру класса Entity, а самому объекту класса Entity, который является экземпляром класса Class.
Переменные экземпляра класса доступны напрямую только внутри методов класса.
В чем разница между переменными класса и переменными экземпляра класса?
Основное различие заключается в поведении при наследовании: переменные класса разделяются между классом и всеми его подклассами, в то время как переменные экземпляра класса принадлежат только одному конкретному классу.
Переменные класса в некотором роде можно рассматривать как глобальные переменные в контексте иерархии наследования со всеми вытекающими проблемами. Например, переменная класса может быть (случайно) переприсвоена любым из подклассов, что повлияет на все остальные классы:
class Woof
@@sound = "woof"
def self.sound
@@sound
end
end
Woof.sound # => "woof"
class LoudWoof < Woof
@@sound = "WOOF"
end
LoudWoof.sound # => "WOOF"
Woof.sound # => "WOOF" (!)
Или класс-предок может быть позже открыт заново и изменен, что приведет к неожиданным эффектам:
class Foo
@@var = "foo"
def self.var
@@var
end
end
Foo.var # => "foo" (as expected)
class Object
@@var = "object"
end
Foo.var # => "object" (!)
Поэтому, если вы точно не знаете, что делаете, и вам не требуется именно такое поведение, лучше использовать переменные экземпляра класса.
Есть ли в Ruby методы класса?
Синглтон-метод объекта класса называется методом класса. (На самом деле метод класса определяется в метаклассе, но это происходит практически незаметно). Другой способ взглянуть на это — сказать, что метод класса — это метод, получателем которого является класс.
Все сводится к тому, что вы можете вызывать методы класса, не имея экземпляров этого класса (объектов) в качестве получателя.
Давайте создадим синглтон-метод класса Foo:
class Foo
def self.test
"this is foo"
end
end
# It is invoked this way.
Foo.test # => "this is foo"
В этом примере Foo.test является методом класса.
Методы экземпляра, определенные в классе Class, могут использоваться как методы класса для каждого(!) класса.
Что такое синглтон-класс?
Синглтон-класс (singleton class) — это анонимный класс, который создается путем наследования класса, связанного с конкретным объектом. Синглтон-классы — это еще один способ расширения функциональности только одного объекта.
Возьмем скромный Foo:
class Foo
def hello
"hello"
end
end
foo = Foo.new
foo.hello # => "hello"
Теперь допустим, нам нужно добавить функциональность на уровне класса только для этого одного экземпляра:
class << foo
attr_accessor :name
def hello
"hello, I'm #{name}"
end
end
foo.name = "Tom"
foo.hello # => "hello, I'm Tom"
Foo.new.hello # => "hello"
Мы настроили foo без изменения характеристик Foo.
Что такое функция модуля?
This section or parts of it might be out-dated or in need of confirmation.
Функция модуля — это приватный синглтон-метод, определенный в модуле. По сути, она похожа на метод класса тем, что её можно вызвать с использованием нотации Module.method:
Math.sqrt(2) # => 1.414213562
Однако, поскольку модули могут быть подмешаны в классы, функции модуля также могут использоваться без префикса (именно так все функции Kernel становятся доступными объектам):
include Math
sqrt(2) # => 1.414213562
Используйте module_function, чтобы сделать метод функцией модуля.
module Test
def thing
# ...
end
module_function :thing
end
В чем разница между классом и модулем?
Модули — это коллекции методов и констант. Они не могут создавать экземпляры. Классы могут создавать экземпляры (объекты) и имеют состояние для каждого экземпляра (переменные экземпляра).
Модули могут быть подмешаны (mixed in) в классы и другие модули. Константы и методы подмешанного модуля смешиваются с собственными константами и методами класса, дополняя его функциональность. Однако классы нельзя подмешить во что-либо.
Класс может наследоваться от другого класса, но не от модуля.
Модуль не может наследоваться ни от чего.
Можно ли создавать подклассы модулей?
Нет. Однако модуль может быть включен в класс или другой модуль, чтобы имитировать множественное наследование (механизм примесей).
Это не создает подкласс (для чего потребовалось бы наследование), но создает отношение is_a? между классом и модулем.
Дайте мне пример примеси (mixin)
Модуль Comparable предоставляет различные операторы сравнения (<, <=, ==, >=, >, between?). Он определяет их через вызовы общего метода сравнения <=>. Однако сам он не определяет <=>.
Допустим, вы хотите создать класс, в котором сравнение основано на количестве ног у животного:
class Animal
include Comparable
attr_reader :legs
def initialize(name, legs)
@name, @legs = name, legs
end
def <=>(other)
legs <=> other.legs
end
def inspect
@name
end
end
c = Animal.new("cat", 4)
s = Animal.new("snake", 0)
p = Animal.new("parrot", 2)
c < s # => false
s < c # => true
p >= s # => true
p.between?(s, c) # => true
[p, s, c].sort # => [snake, parrot, cat]
Все, что должен сделать Animal, — это определить собственную семантику для оператора <=> и подмешить модуль Comparable. Методы Comparable теперь становятся неотличимы от методов Animal, и ваш класс внезапно обретает новую функциональность. А поскольку один и тот же модуль Comparable используется многими классами, ваш новый класс будет иметь согласованную и понятную семантику.
Почему существует два способа определения методов класса?
Вы можете определить метод класса внутри определения класса или на верхнем уровне.
class Demo
def self.class_method
end
end
def Demo.another_class_method
end
Между ними есть только одно существенное различие. В определении класса вы можете обращаться к константам класса напрямую, так как они находятся в области видимости. На верхнем уровне вам придется использовать нотацию Class::CONST.
В чем разница между include и extend?
This section or parts of it might be out-dated or in need of confirmation.
include подмешивает модуль в класс или другой модуль. Методы из этого модуля вызываются в стиле функций (без получателя).
extend используется для включения модуля в объект (экземпляр). Методы модуля становятся методами объекта.
Что означает self?
self — это текущий исполняемый получатель, объект, к которому применяется метод. Вызов метода в стиле функции подразумевает self в качестве получателя.