2019
Do not Repeat Yourself - при разработке программного обеспечения необходимо устранять ненужное дублирование
Если логика изменится, то нужно исправить только в 1 месте
В каждом из этих случаев следует избегать дублирования
Классы описывают общую логику действий для множества различных объектов: методы класса доступны всем объектам
Вызов метода в Ruby - передача сообщения, состоящего из
Когда объект получает сообщение, то оно передаётся классу данного объекта для обработки
Поиск метода происходит только по имени, из аргументов учитывается только лишь их количество
Механизм наследования позволяет создавать новые версии класса, являющиеся его уточнением или специализацией
<
Child
является подклассом Parent
self
содержит ссылку на объект, на котором вызван методclass Parent
def say_hello
puts "Hello from #{self}"
end
end
parent = Parent.new
puts parent.say_hello
class Child < Parent
end
child = Child.new
puts child.say_hello
superclass
Object
Object
является BasicObject
, у которого нет суперклассаputs "Child superclass: #{Child.superclass}"
puts "Parent.superclass: #{Parent.superclass}"
puts "Object.superclass: #{Object.superclass}"
puts "BasicObject.superclass "\
"#{BasicObject.superclass.inspect}"
Напомним, что общение между объектами в Ruby напоминает отправку сообщения: в сообщении указывается название метода и его аргументы.
Когда объект получает сообщение, то он начинает поиск метода, которому можно передать данное сообщение:
Первый найденный метод в данной цепочке будет вызван и ему будут переданы все переданные аргументы
super
, который является альтернативным названием родительского методаObject
to_s
, преобразующий текущий объект к строкеclass Person
def initialize(name)
@name = name
end
end
person = Person.new("Michael")
puts person # <Person:0x007fc812839550>
class Person
def initialize(name)
@name = name
end
def to_s
"Person named #{@name}"
end
end
person = Person.new("Michael")
puts person # Person named Michael
При написании сложных приложений разработчики зачастую сталкиваются с проблемой роста:
В Java
проблема решается обязательным выделением пакетов, а в последней версии и модулей со встроенными версиями.
Модуль - замкнутое именованное пространство
module Trig
PI = 3.141592654
def Trig.sin(x) # Определяем метод модуля
# ..
end
def Trig.cos(x)
# ..
end
end
puts Trig::PI # Доступ к константе
puts Trig.sin(Trig::PI/4) # Вызов метода
::
module Moral
VERY_BAD = 0
BAD = 1
def Moral.sin(badness)
# ...
end
end
y = Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)
Определение методов модулей похоже на определение методов классов (не методов экземпляров классов). Можно даже использовать синтаксис с ключевым словом self
module ModuleWithBigName
def ModuleWithBigName.greet(name)
puts "Hello, #{name.upcase}"
end
def self.info
'I am working'
end
end
ModuleWithBigName.greet('mariya')
puts ModuleWithBigName.info
Оба метода в примере являются методами одного модуля
К константам, которые можно определить в модуле, относятся также модули и классы
Ввиду того, что модули тоже являются константами, то их содержимое можно определять в нескольких файлах, эффективно создавая замкнутые пространства имён
Содержимое файла lib/application/car.rb
Содержимое файла lib/application/house.rb
Предположим, что мы разрабатываем библиотеку для создания графического пользовательского интерфейса и мы определили следующие сущности: базовую и двух наследников
Если мы захотим создать новый элемент, который реализует прокручиваемый список элементов, то нам будет необходимо унаследоваться от последних двух классов. Вопросы:
Модули позволяют решить вопросы множественного наследования, реализуя возможность агрегирования: модули можно примешивать к определениям классов
module Debug
def who_am_i? # Экземпляр метода
"#{self.class.name} (id: #{self.object_id})"
end
end
class Test
include Debug # Примешивание модуля Debug
end
Test.new.who_am_i?
include
include
не подключает файл, а только ссылается на описанный ранее модуль. Если примесь определена в отдельном файле, то его необходимо подключитьinclude
не копирует методы из модуля в класс, а только лишь добавляет ссылку в класс
include
подключает только лишь методы экземпляра, для подключения методов модуля в методы класса необходимо использовать метод extend
extend
module Mixin
def self.class_method
puts 'In the class method'
end
def instance_method
puts 'In the instance method'
end
end
class Test
extend Mixin
end
Test.class_method
test = Test.new
test.instance_method # Не сработает
Множественное наследование (C++)
Один класс-родитель (Java < 8, C#)
В Java 8 интерфейсы могут содержать код, который может выполняться. Это открывает возможности по использованию их в качестве примесей, однако такой практики пока ещё не сложилось.
Язык Ruby включает в себя ряд полезных примесей, которые могут пригодится при создании собственных объектов
Примесь Comparable
опирается на то, что в классе будет реализован метод сравнения <=>
. Данный метод берёт ссылку на другой объект, сравнивает и возвращает
Большинство встроенных типов данных реализуют данный метод.
Используя этот метод примесь добавляет в ваш класс следюущие методы: <
, <=
, ==
, >
, >=
, between
, clamp
.
class SizeMatters
include Comparable
attr :str
def <=>(other)
str.size <=> other.str.size
end
def initialize(str)
@str = str
end
end
s1 = SizeMatters.new("Z")
s2 = SizeMatters.new("YY")
s3 = SizeMatters.new("XXX")
s1 < s2 #=> true
s4.between?(s1, s3) #=> false
Модуль Enumerable
предоставляет большое число методов для работы с данными, которые можно перечислить.
Классу, использующему модуль Enumerable необходимо реализовать следующие методы:
each
для обхода всех элементов коллекции<=>
После примеси модуля становятся доступными все его методы:
map
find
reduce
sort
Из рассмотренных выше примесей мы можем вынести следующие уроки:
Comparable
таким свойством явился метод <=>
Enumerable
таким свойством явился метод each
Методы, примешанные к классу, могут взаимодействовать не только с методами, но также и с переменными экземпляра
Не следует разрабатывать модулей, которые оперируют состоянием переменных экземпляра напрямую
Когда у вас появляется возможность использовать примеси, то должен встать вопрос: как происходит поиск методов среди всех связных компонент? Порядок достаточно простой:
Зависит ли порядок поиска методов в примесях от порядка их включения в класс?
Механизм наследования позволяет определить чёткую иерархию свойств объектов. Для любого класса-ребёнка должно выполняться правило: класс-ребёнок является классом-родителем. Т.е. в программе можно заменить класс-родитель на класс-ребёнок и приложение должно продолжить правильно функционировать.
В большинстве случаев реального мира мы имеем дело с предметами, обладающими множеством различных свойств и множеством контрагентов. То есть объект живёт в некоторой среде и комбинирует в себе качества множества различных компонент. Такое поведение обычно сложно представить в виде подмножества какого-то конкретного класса.
Простое правило, которое следует использовать при проектировании классов: «композиция лучше наследования»