Оглавление | 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.

Классы и модули

Может ли определение класса повторяться?

Класс можно определять многократно. Каждое определение добавляется к предыдущему. Если метод переопределяется, старый метод замещается и теряется.

Существуют ли переменные класса?

Да. Переменная с префиксом из двух символов «собака» (@@) — это переменная класса, доступная как в методах экземпляра, так и в методах класса.

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 в качестве получателя.