Глава дев’ята
Модулі та домішки…
Як згадано у попередніх главах, кожен клас у Ruby має одного найближчого батька, хоча кожен батько може мати багато дітей.
Обмежуючи ієрархію класів однією лінією наслідування, Ruby уникає дакількох проблем, які можуть виникати у таких мовах програмування (як от C++), які which дозволяють множинне успадкування.
Коли клас має так багато батьків, як і дітей та їхніх батьків, а діти мають багато інших батьків та дітей, ви ризикуєте потрапити в заплутану мережу (чи може навіть павутинну?) замість того, щоб мати чітку, добре організовану ієрархію, яка вам і потрібна.
Тим не менше, часом виникають ситуації, коли корисно, щоб клас міг імплементувати функціональність, яка є спільною з більше, ніж одним попередньо визначеним класом.
Наприклад, Sword
(меч) може належати до типу Weapon
(зброя), але крім того належати й до типу Treasure
(знахідка); House
(будинок) може належати не лише до типу Building
(будівля), але й до Investment
(інвестиція) і так далі.
Модуль — це як клас…
Ruby вирішує цю проблему, надаючи нам модулі. На перший погляд, модуль виглядає схожим на клас. Так само, як і клас, він містить константи, методи та класи.
Ось простий модуль:
module MyModule
GOODMOOD = "веселий"
BADMOOD = "сварливий"
def greet
return "Я #{GOODMOOD}. А ти як?"
end
end
Як бачите, він містить константу GOODMOOD
та метод екземпляру greet
. Щоб перетворити цей модуль на клас, вам потрібно лише замінити у цьому оголошенні слово module
на class
.
Методи модулів
На додачу до методів екземпляру модуль може мати і методи модуля, в яких перед іменем стоїть назва модуля:
def MyModule.greet
return "Я #{BADMOOD}. А ти як?"
end
Незважаючи на схожість, класи мають дві суттєві переваги, яких не мають модулі: екземпляри (instances) та успадкування (inheritance). Класи мають екземпляри (об’єкти), батьківські класи (суперкласи) та дочірні класи (субкласи); модулі не мають нічого подібного.
Це змушує нас поставити собі запитання: якщо ми не можемо створити об’єкт з модуля, для чого тоді вони?
Це ще одне питання, на яке ми можемо відповісти двома словами: простори (namespaces) та домішки (mixins). Домішки в Ruby дають нам можливість працювати з маленькою проблемою множинного успадкування, про яку я згадував раніше. Ми повернемось до домішок згодом, а поки що давайте подивимось на простори імен.
Модулі як простори імен
Ви можете думати про модуль, як про свого роду, іменовану обгортку довкола множини методів, констант та класів. Різні частини коду всередині цього модуля поділяють спільний простір імен - це означає, що вони всі видимі для один одного, але невидимі для коду, що поза цим модулем.
Бібліотека класів Ruby ряд модулів, як от Math
та Kernel
. Модуль Math
містить математичні методи, як от sqrt
, щоб повертати квадратний корінь або константи, такі як PI
. Модуль Kernel
містить різні методи, які ми вже раніше використовували: print
, puts
та gets
.
Константи
Константи — це як змінні, за винятком того, що їх значення не можна (і не слід!) змінювати. Фактично, змінити значення константи в Ruby можливо (незрозуміло!), проте це точно не заохочується, а Ruby зробить вам попередження, якщо ви спробуєте таке зробити. Зауважте, що константи починаються з великої літери.
Припустимо ми маємо такий модуль:
module MyModule
GOODMOOD = "веселий"
BADMOOD = "сварливий"
def greet
return "Я #{GOODMOOD}. А ти як?"
end
def MyModule.greet
return "Я #{BADMOOD}. А ти як?"
end
end
Ми можемо звернутись до константи, використовуючи ::
, ось так:
puts(MyModule::GOODMOOD)
Схожим чином ми можемо звернутись до методів модуля з допомогою крапкового запису – це визначено ім’ям модуля та методу розділених крапкою. Наступний приклад виведе: Я сварливий. А ти як?
:
puts(MyModule.greet)
Методи екземпляру модуля
Це ставить нас перед проблемою: як отримати доступ до метода екземпляру, greet
. Оскільки модуль визначає закритий простір імен, код поза цим модулем не може побачити метод greet
, тому таке не спрацює:
puts(greet)
Якщо б це був клас, а не модуль, ми б, напевно, створили об’єкти цього класу з допомогою методу new
– і кожен окремий об’єкт (кожен екземпляр цього класу), мав би доступ до методів екземпляру. Проте, як я вже раніше казав, ви не можете створити екземпляри модулів. То яким же боком ми маємо дістати наші методи екземпляру? Ось, де таємничі домішки з’являються приходять на допомогу…
Включені модулі та домішки
Об’єкт може мати доступ до методів екземпляра модуля, якщо просто включити модуль з допомогою методу include
. Якщо ви включите MyModule
у вашу програму, все всередині цього модуля почне існувати у поточній області видимості. Таким чином метод greet
модуля MyModule
тепер стане доступним:
include MyModule
puts(greet)
Процес включення модулів у клас також називають змішуванням (mixing in) модуля – це пояснює, чому включені модулі часто називають домішками (mixins).
Коли ви включаєте об’єкти в оголошення класу, будь–який об’єкт, який створений від цього класу, матиме можливість використовувати методи екземпляру включеного модуля так, ніби вони були визначені в самому класі.
class MyClass
include MyModule
def sayHi
puts(greet)
end
def sayHiAgain
puts(MyModule.greet)
end
end
Не лише методи цього класу мають доступ до метода greet
з MyModule
, але й усі об’єкти, які створені від цього класу, ось так:
ob = MyClass.new
ob.sayHi
ob.sayHiAgain
puts(ob.greet)
Якщо коротко, то модулі можуть використовуватись для групування пов’язаних методів, констант та класів під іменованою областю видимості. У цьому розуміння, про модулі можна думати, як про закриті одиниці коду, які можуть спростити створення бібліотек для повторного використання коду.
З іншого боку, ви можете бути більш зацікавленні у використанні модулів в якості альтернативи множинному успадкуванню. Повертаючись до прикладу, про який я згадував на початку цієї глави, давайте припустимо, що клас Sword
, який має тип, як Weapon
, так і Treasure
. Можливо, Sword
є нащадком класуWeapon
(тому успадковує такі методи як deadliness
(смертоносність) та power
(сила)), проте він також повинен мати методи з Treasure
(такі, як value
(значення) та insurance_cost
(вартість страхування)). Якщо ви визначете ці методи всередині модуля Treasure
, замість класу Treasure
, клас Sword
міг би включати модуль Treasure
для того, щоб додати (змішати) методи Treasure
та власні методи класу Sword
.
Зауважте, між іншим, що до будь–якої змінної, яка є локальною для модуля, не можна звернутись ззовні. Це справджується, навіть якщо метод всередині модуля пробує звернутись до локальної змінної, і цей метод викликається кодом поза модулем – наприклад, коли модуль домішаний через включення. Програма mod_vars.rb
ілюструє це.
Включення модулів з файлів
До цього ми змішували модулі, які були оголошені всередині одного вихідного файлу. Часто корисніше оголошувати модулі у окремих файлах та включати їх за потреби. Найперше, що ви маєте зробити, щоб використовувати код з іншого файлу — завантажити цей файл використовуючи метод require
, ось так:
require("testmod.rb")
Завантажений файл має розташовуватися у поточній директорії, у місцях для пошуку модулів або у наперед визначеному масиві $:
. Ви можете додати папку до цього масиву з допомогою звичного методу для додавання в кінець масиву <<
, таким чином:
$: << "C:/mydir"
Метод require
повертає значення true
, якщо вказаний файл завантажений успішно, інакше він повертає false
. Якщо ви сумніваєтесь, можете просто вивести результат:
puts(require("testmod.rb"))
Наперед визначені модулі
Наступні модулі є вбудованими в інтерпретатор Ruby:
Comparable, Enumerable, FileTest, GC, Kernel, Math, ObjectSpace, Precision, Process, Signal
Найважливішим попередньо визначеним модулем є Kernel
, який, як сказано раніше, надає багато стандартних методів Ruby, таких як gets
, puts
, print
та require
. Загалом, як і більшість з бібліотеки класів Ruby, Kernel
написаний мовою C. Тоді як Kernel
, фактично, вбудований в інтерпретатор Ruby, концептуально його можна розглядати як змішаний модуль, так само як і звичайна домішка в Ruby, він робить свої методи безпосередньо доступними для класів, які завантажують його. Оскільки він є змішаним з класом Object, від якого походять всі класи в Ruby, методи з Kernel
є загальнодоступними.