ООП в Ruby

Ruby — объектно-ориентированный язык программирования, поэтому знание парадигмы объектно-ориентированного программирования (ООП) является обязательным.

Тема объектно-ориентированного программирования достаточно сложна, поскольку в ней переплетаются множество подтем, которые, разделить нельзя, поэтому данная глава будет иметь итерационной подход к изложению материала, то есть мы будем двигаться по спирали захватывая все больший и большый объем информации. Что есть Объектно-ориентированное программирование?

Объектно — ориентированное программирование — это программирование основанное на использовании объектов — экземпляров абстрактных типов (классах).
Объект в ООП — это программная модель какого-то реально существующего объекта, например вас или меня, при этом будучи объектами реального мира мы будем являться экземплярами некоторого типа (вида) — Человека Разумного. Объекты являющиеся экземплярами одного класса называются родственными или братьями, а класс от которого они произошли, если провести аналогию, является шаблоном ДНК человека в котором описываются все общие моменты производных объектов, однако каждый объект может иметь свои отличия от другого объекта того же типа. Все люди одинаковые в том, что имеют две ноги, два глаза и штуку на животе именуемую пупком, однако у всех людей различается цвет глаз, отпечатки пальцев, имя, характер, лицо. Не смотря на эти различия между людьми, мы все-таки отличаем других людей, скажем от макак или павианов. Все общее в людях и общий набор свойств определяется в классе, а все различное определяется в самих объектах.

Для начала давайте создадим код, на котором мы будем разбирать примеры,он будет состоять из двух классов: Monkey и Human, где Human, согласно революционной эволюционной теории Чарльза Дарвина наследуется от класса Monkey:

class Monkey
def initialize(height, weight)
@height = height
@weight = weight
end

def height
@height
end

def weight
@weight
end

def height=(height)
@height = height
end

def weight=(weight)
@weight = weight
end
end

class Human < Monkey
def initialize(height, weight, name)
super(height, weight)
@name = name
end

def name
@name
end

def name=(name)
@name = name
end
end

m = Monkey.new(120,55)
h = Human.new(180, 90, 'Ivan')

Что есть Инкупсуляция?

Инкапсуляция — это самый большой и жирный кит объектно-ориентированного программирования. Инкапсуляция заключается в том, что присущие функциональному программирования данные и функции над ними превратились в свойства и методы объекта, которые заключены в объект (запаяны в капсулу). Доступ к этим свойствам и методам доступен только через специально определяемые интерфейсы, таким образом скрывается внутреннее устройство объекта. если вы заходите покушать, вам не нужно знать все методы вашего организма по перевариванию пищи,вам не нужно обращаться к языку, чтобы тот чувствовал вкус,к слюнным железам, чтобы те выделяли слюну смачивающую пищу, к нижней челюсти, чтобы та пережевывала пищу до необходимой кондиции, снова к языку и пищеводу, чтобы пища попала в желудок, затем к желудку, чтобы тот обработал пищу желудочным соком и так далее. Все, что вам нужно — это воспользоваться специальным интерфейсом — ртом, положив туда пищу, все, внутренние методы вашего организва запустятся сами этим одним единственным интерфейсом (методом — аксессором, от access — доступ). Ваши свойства, как экземпляра Хомо Сапиенса, это размеры элементов вашего тела, цвет кожи, цвет глаз, родинки и веснушки, ваше имя и фамилия, ваше физическое, духовное и психологическое состояние. Вы хороший, здоровый, темноглазый, бледнолицый, девяносто килограммовый и сто-восьмидесята сантиметровый Иван — и все, что я перечислил — это ваши свойства. Ваши методы — это ходьба, бег, употребление пищи, сон, чтение, писание, программирование и так далее. Свойства к которым можно получить доступ называются открытыми, открыты они не из-за какой-то своей особенности, а из-за того, что для них определены методы — аксессоры. Посмотрте код Monkey и вы увидите там четыре метода аксессора: #weight, #height, #weight= и #height=. Первых два называются ридерами (от read — читать), они занимаются тем, что предоставляют вам текущее значение одноименных свойств объекта @weight и @height. Другие два метода, #height= и #weight= являются аксессорами — врайтерами (от write — писать) и из задача состоит в определении (записи) нового значения свойствам объекта. Без методов аксессоров вы не можете получить доступ к свойствам объекта, вы не можете знать имя девушки, и есть ли у нее парень, пока не воспользуетесь соответствующими методами аксессорами: #name и #has_boyfriend?. Аксессоры read и write также часто называют get и set — аксессорами (от get — получать, set — устанавливать).

Расследование что есть Наследование

Наследование — чуть менее упитанный кит ООП, тем не менее он очень важен. Наследование заключается в эволюции классов. Как человек эволюционировал из обезъяны, так и один класс может наследоваться от другого. Наследование заключается в передаче всей структуры класса Monkey в класс Human, при этом наследование реализуется очень просто, при определении класса Human достаточно дописать < Monkey, что и указывает на наследование класса Human от класса Monkey. Говорят, что Monkey наследует Human, а Human наследуется от Monkey. Говорят, что Monkey — супер класс или родительский класс, а Human — подкласс, субкласс или дочерний класс. На практике это означает, что для Human полностью копируется реализация класса Monkey. Такое копирование и есть Наследованием.

Наследование — очень полезная вещь, так как: предоставляет возможность повторного использования кода, спасает от повторений в коде. Скажем имея класс Human, мы можем наследоваться от него классами WhiteHuman и BlackHuman, которые будут иметь абсолютно одиинаковые свойства, за исключением цвета кожи и формы носа. Вместо написания двух новых классов, вы просто наследуетесь ими от базового класса — Human дополняя его новыми свойствами и методами, либо переписывая старые.

class BlackHuman < Human
def initialize(height, weight, name)
super(height, weight, name)
@skin_color = :black
end

def skin_color
@skin_color
end

def skin_color=(skin_color)
@skin_color = skin_color
end
end

На случай, если темнокожего человека зовут Майкл Джексон, мы определяем метод-аксессор #skin_color=, который позволяет изменить цвет кожи.

Полиморфизм — все течет, все меняется

Полиморфизм — самый щуплый кит объектно-ориентированного программирования, который тесно связан с китом наследования и делает наследование еще более мощным. Полиморфизм (поли — много, морфе — форма) заключается в том, что наследуясь, в классе — потомке (дочернем классе или подклассе или субклассе или …) вы можете переопределять унаследованные свойства и методы. Во всех трех примерах выше имеется метод #initialize. Метод #initialize, согласно договоренности, является тем методом, что автоматически выполняется при создании нового экземпляра класса, такие методы еще называют методами — конструкторами. В классах Monkey и Human данный метод занимается тем, что принимает данные переданные в метод .new при создании нового объекта и устанавливает эти данные в свойства создаваемого объекта. Поскольку у экзмепляров классов Monkey и Human набор свойств разный, то и разное количечество аргументов должно быть принято, от чего следует в классе Human переопределить данный метод таким образом, чтобы он мог задавать дополнительные свойства. В классе BlackHuman мы также переопределяем метод #initialize, однако он сам устанавливает свойству @skin_color по умолчанию значение :black. В обоих классах Human и BlackHuman метод #initialize снабжен выражением super. Super — это очень полезное выражение, которое позволяет в контексте метода вызывать одноименный метод из родительского класса, другими словами, super позволяет реализовать что-то вроде «наследования методов» при котором мы в контексте переопределенного метода, вызываем старый метод и дополняем его. Все эти операции по переопределению и носят имя полиморфизм.

Области видимости и типы переменных

Невозможно объяснять более-менее сложные вещи не объяснив о том, что такое область видимости и какие переменный бывают.
Область видимости — это способность переменной быть использованой в контексте определенного фрагмента кода. Класс, объект, процедура, метод, код, вложеный в блоки условных операторов и циклов — все это может быть областью видимости. В Ruby существует 5 типов переменных:

  • variable — локальная переменная, она доступна только в той области видимости, где была определена, а также во всех вложенных областях видимости.
  • @variable - переменная объекта (экземпляра класса), имена таких переменных должны начинаться с одного знака @ — это еще одно соглашение в Ruby, которое вы обязаны знать. переменные экземпляра класса доступны только в том объекте, где они определены и вроженных в него областях видимости.
  • @@variable — переменная класса, имена таких переменных должны начинаться с двух символов @. Их область видимости — класс в котором они определены и все экземпляры данного класса.
  • $variable — глобальные переменные, их имена должны начинаться с символа $, а область видимости — вся программа.
  • Constant - константы это не переменные, но предназначение похожее — хранить ссылку на объект. Имена констант должны начинаться с большой буквы. Область видимости константы такая же как и у глобальной переменной, единственное отличие заключается в том, что значение константы следует использовать для «статики». При попытке изменить значение константы, значение будет изменено, однако будет возвращено предупреждение. В большинстве других языков программирования констаты не могут переопределяться.

Создание объектов

В Ruby существут предопределенные типы данных, которые были рассмотрены в прошлой главе и абстрактные — то, что мы рассматриваем в данной главе RubyDev Ruby Tutorial.
Отличие
в создании базовых (предопределенных) типов и абстрактных заключается в том, что для базовых типов существует специальный упрощенный синтаксис:

5.object_id #=> 11
"string".object_id #=> 82742600
[1,2,3].object_id #=> 82800010

Просто используя тот или иной литерал, мы автоматически создаем соответствующий литералу объект. Работая с абстрактными типами данных, нам необходимо явно определять объект при помощи метода .new класса, примеры:

m = Monkey.new(120,55)
h = Human.new(180, 90, 'Ivan')

m и h являются объектами, соответственно, классов Monkey и Human, что означает, что они имеют тип Monkey и Human. На самом деле m и h являются всеголишь переменными ссылающимися на реальные объекты в памяти компьютера, которые не имеют имен, но имеют уникальные идентификаторы объектов, по которым к ним происходит обращение. Несмотря на это m и h, для простоты можно также называть объектами.

Создавая объект (экземпляр класса) нам необходимо воспользоваться методом .new. Метод .new принимает аргументы для создания класса, которые затем могут быть использованы в методе #initialize и создает новый объект. Методы классов и методы объектов

Вы наверняка заметили, что в данном учебнике перед именами одних методов я ставлю знак #, а перед именами других я ставлю точку. Это такая договоренность, согласно которой имена методов вне текста программы начинаются с # если метод принадлежит объекту и с . , если метод принадлежит классу. Метод класса отличается от метода объекта тем, что приемником метода класса является класс, а приемником метода экземпляра класса является экземпляр класса (объект). Хочу заметить, что в Ruby даже сами классы являютя объектами. Отличие классов от объектов заключается в том, что каждый класс является экземпляром базового класса Class. Любой метод объявленный следущим образом:

def method_name()
end

является методом экземпляра класса. Для того, чтобы объявить метод класса, вам необходимо использовать следущую конструкцию:

def self.method_name()
end //, либо следущую:

class BlackHuman < Human

class << self
def obj_count
count = 0
ObjectSpace.each_object(self){|obj| count += 1}
end
end

def initialize(height, weight, name)
super(height, weight, name)
@skin_color = :black
end

def skin_color
@skin_color
end

def skin_color=(skin_color)
@skin_color = skin_color
end
end

bh = BlackHuman.new(180,90,"John")
bh2 = BlackHuman.new(185,90,"Robert")
bh3 = BlackHuman.new(170,70,"Thomas")

puts BlackHuman.obj_count # 3

Как видно из примера, вызов методов класса не отличается от вызова методов объекта, единственное отличие — это приемник метода.