Модули и миксины
Mixin
Модули по своему функционалу не так сильно отличаются от объектов. Мы можем объявлять модули как классы, можем обявлять внутри этих модулей переменные как и в классе, мы можем обявлять методы как и в классе, но в отличии от классов, мы не можем создавать объекты модулей. Какой тогда смысл от них ? Все просто, мы можем "подмешивать" модели в наши классы, тем самым обогащая их новым функционалом.
# Объявление модуля
module MyModule
# метод модуля
def print_hello
puts 'hello world'
end
end
class MyClass
# для того, чтобы подмешать модуль испольуется ключевое слово include
include MyModule
end
instance = MyClass.new
# Как можем заметить метод print_hello отсутствует в нашем классе
# но присутствует в нашем модуле
instance.print_hello # = hello world
Подмешивание
на английском переводится как mix
, отсюда и происходит слово mixin
, поэтому можно сказать что mixin
- это возможность использовать наши модули как инструмент расширения функциональности наших классов.
Основное отличие миксин от наследования заключается во множественном подмешивании. При наследовании класса от другого класса мы можем наследоваться только один раз, в то время как подмешивать миксины в класс мы можем сколько угодно.
module ModuleOne
def say_hi
puts 'Hi'
end
end
module ModuleTwo
def say_bye
puts 'Bye'
end
end
class MyClass
include ModuleOne
include ModuleTwo
end
instance = MyClass.new
# метод полученый из модуля ModuleOne
instance.say_hi # = Hi
# метод полученный из модуля ModuleTwo
instance.say_bye # = Bye
В Ruby существуют встроенные модули, которые позволяют расширить функциональность наших классов. Например модуль Comparable
, который дает возможность сравнивать объекты друг с другом, если мы подмешиваем этот модуль и переопределяем поведение метода <=>
. Метод принимает в аргументах объект с которым будет происходить сравнение и должен:
вернуть 1, если наш объект больше объекта переданного в аргументе
вернуть -1, если наш объект меньше объекта переданного в аргументе
вернуть 0, если они равны
После этого нам становятся доступны такие логические операции как >
, <
, >=
и <=
.
class MagicBall
include Comparable
attr_reader :value
def initialize(value)
@value = value
end
def <=>(another_ball)
# Если value нашего объекта меньше value у сравниваемого
if value < another_ball.value
-1 # то мы должны вернуть отрицательное число
# Если value нашего объекта больше value у сравниваемого
elsif value > another_ball.value
1 # то мы должны вернуть положительное число
else
0 # иначе эти объекты равны, а значит мы должны вернуть 0
end
end
end
first_ball = MagicBall.new(1000)
second_ball = MagicBall.new(2000)
# > или < или >= или <= это тоже методы, которые добавляет в наш класс модуль Comparable
# Соответственно, без этого модуля мы получим ошибку о неизвестном методе
p first_ball > second_ball # = false
Все примитивные дата классы такие как строки, числа и тд уже имеют в себе переопределенный метод <=>
, поэтому мы можем использовать этот метод у этих объектов для упрощения написания кода.
class MagicBall
include Comparable
attr_reader :value
def initialize(value)
@value = value
end
def <=>(another_ball)
# такое же поведение как и выше, только логику мы переложили
# на переопределенный метод <=> внутри объекта Integer
value <=> another_ball.value
end
end
first_ball = MagicBall.new(1000)
second_ball = MagicBall.new(2000)
# > или < или >= или <= это тоже методы, которые добавляет в наш класс модуль Comparable
# Соответственно, без этого модуля мы получим ошибку о неизвестном методе
p first_ball > second_ball # = false
Помимо Comparable
, есть также модуль Enumarable
, который добавляет множество методов для нашего класса, чтобы работать с ним как с коллекцией. Для этого нам необходимо при подмешивании определить метод each
, который должен применять блок кода к каждому элементу, который содержится внутри нашего класса. Блоки кода будут разбираться в дальнейших уроках, сейчас главное понимать не как реализовывать метод each
, а что нам дает его переопределние при использовании модуля Enumarable
.
Взамен мы получаем такие методы как count
, min
, max
, find
, map
и тд. Думаю вы заметили, что такие методы вы уже встречали в массивах ? На самом деле любая коллекция в Ruby имеет внутри себя модуль Enumarable
. К слову, цикл for
работает с объектами благодаря наличию у них модуля Enumarable
.
class Collection
include Enumarable
attr_reader :arr
def initialize(arr)
@arr = arr
end
# переопределяем метод each
def each
arr.each { |element| yield(element) }
end
end
collection = Collection.new([0, 10, 15])
# благодаря Enumarable мы можем использовать объект внутри цикла for
for element in collection
puts element # сначала распечатается 0, потом 10, потом 15
end
Более подробно с встроенными модулями вы сможете ознакомиться в дополнительном материале.
Константы
Кроме того, модули, как и классы, часто используются для организации однотипных констант, но сами по себе модули гораздо более легковестные, чем классы, поэтому их использование для этих целей считается хорошей практикой.
module Color
GREEN = 0
BLACK = 1
RED = 2
end
p Color::GREEN # = 0
Структуризация
Помимо прочего, модуль может содержать внутри себя классы, что позволяет нам организовывать структуру нашего кода. Особенно полезно когда наши классы имеют одинаковое название, за счет чего модули позволяют разделять эти классы между собой
module School
class User
def initialize(name, class_number)
@name = name
@class_number = class_number
end
end
end
module University
class User
def initialize(name, course_number)
@name = name
@course_number = course_number
end
end
end
# объект класса User, который находится внутри модуля School
school_user = School::User.new('mike', 9)
# объект класса User, который находится внутри модуля University
university_user = University::User.new('john', 10)
# заметьте, оба классы абсолютно разные и не связаны между собой, кроме одинакового названия
задание animals
Внимание ! Это и последующие задания данного модуля с названием animals
необходимо выполнять в одной и той же директории в корне модуля - 3_oop/animals
Изучите дополнительный материал про модуль
Comparable
и с помощью него научитесь сравнивать объектыCat
между собой по их весу.Напишите в
main.rb
пример сравнения ваших котов, убедитесь, что оно работаетПопробуйте теперь сравнить объект класса
Cat
и классаDog
, какие выводы можно сделать ?
Задание company
Внимание ! Это и последующие задания данного модуля с названием company
необходимо выполнять в одной и той же директории в корне модуля - 3_oop/company
Реализуйте в классе
Company
методы:get_top_salary_staff(count)
- возвращает список зарплат сотрудников, отсортированных по убыванию заданной длиныget_lowest_salary_staff(count)
- возвращает список зарплат сотрудников, отсортированных по возрастанию заданной длинывыозвите эти методы до и после увольнения 50% сотрудников
Дополнительный материал
Last updated