Зміст | 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 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 методи класу?

Singleton-метод об’єкта класу називається методом класу. (Насправді метод класу визначається в метакласі, але це здебільшого прозоро). Інакше кажучи, метод класу — це метод, отримувачем якого є клас.

Зводиться все до того, що методи класу можна викликати без екземплярів цього класу (об’єктів) як отримувача.

Створімо singleton-метод класу 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-клас?

Singleton-клас — це анонімний клас, створений шляхом наслідування класу, пов’язаного з конкретним об’єктом. Singleton-класи — ще один спосіб розширити функціональність, пов’язану лише з одним об’єктом.

Візьмімо скромний 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.

Функція модуля — це приватний singleton-метод, визначений у модулі. По суті, це схоже на метод класу, оскільки його можна викликати у вигляді 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

У чому різниця між класом і модулем?

Модулі — це колекції методів і констант. Вони не можуть створювати екземпляри. Класи можуть створювати екземпляри (об’єкти) і мають стан на рівні екземпляра (змінні екземпляра).

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

Клас може успадковувати інший клас, але не модуль.

Модуль не може наслідувати нічого.

Чи можна успадковувати модулі?

Ні. Однак модуль можна включити до класу або іншого модуля, щоб імітувати множинне наслідування (механізм mixin).

Це не створює підклас (для цього потрібне наслідування), але створює зв’язок 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 як отримувача.