Часть 1: Введение в ООП
Объектно-ориентированное программирование (ООП) - это парадигма программирования. Это позволяет нам представлять сложности реального физического мира в цифровом виде. Языки, поддерживающие некоторый уровень объектной ориентации, распространены повсеместно - они включают Ruby, Python, Javascript, PHP, C ++, C #, Java, Rust, Kotlin, Go и многие другие.
Это первая статья из серии, состоящей из двух частей, в которой рассматривается тема ООП с использованием Ruby для иллюстрации идей и предназначена для тех, кто понимает основы процедурного программирования и изучает ООП.
По своей сути ООП состоит из классов и объектов, созданных из этих классов.
Классы определяют атрибуты и поведение
Класс - это план или шаблон для объектов. Это концепция или идея вещи, а объект - одно физическое проявление этой концепции. Например, возьмем концепцию книги. Представьте себе одно из них. Что ты видишь? Другими словами, что делает книгу книгой?
У книги есть атрибуты
- Некоторое потенциально увлекательное название
- Некоторые авторы, написавшие это
- Некоторое количество страниц
- Какой-то переплет в мягкой или твердой обложке
- Обложка какого-то цвета
- Некоторый измеримый вес
Это все характеристики - они описывают книгу. Переменные экземпляра - это атрибуты отдельной книги (например, название Гарри Поттер и Тайная комната, номер страницы 432, весом 0,74 фунта). Суммарное объединение всех переменных экземпляра отдельной книги составляет состояние этой конкретной книги.
С книгой можно выполнять определенные действия
- Это можно прочитать
- Его можно забрать
- Это можно отложить
- Его можно бросить
- Страницы можно вырывать
- Заметки можно писать на полях
В ООП действия, которые вы можете выполнять с объектом, определены в определении класса. Они называются методами экземпляра и могут вызываться для любого объекта, созданного из класса.
Сделав шаг назад, класс диктует возможные атрибуты или характеристики объектов, которые из него сделаны.
Давайте сосредоточимся на трех ключевых атрибутах - заголовке, авторе и количестве страниц, чтобы создать Book
класс ниже.
class Book def initialize(title, author, pages) @title = title @author = author @pages = pages end end
В нашем простом определении класса Book
есть специальный метод экземпляра initialize
. Это метод конструктора Ruby. Он запускается каждый раз, когда метод класса ::new
вызывается для класса Book
. Обратите внимание, что initialize
- это частный метод экземпляра, что означает, что его нельзя вызывать вне определения класса. Кроме того, если метод инициализации определен для приема параметров, методу ::new
должны быть переданы аргументы (если только параметры, определенные в определении класса, не имеют параметров по умолчанию).
Переменные экземпляра составляют состояние
Вы могли заметить переменные с префиксом @
. Переменные экземпляра обозначаются знаком @
(также известным как сигил). Эти переменные имеют область видимости на уровне объекта - они доступны для всех методов экземпляра, определенных в классе, и каждый объект, созданный из класса, имеет свою собственную копию переменной, которая может содержать собственное значение.
Определив наш класс, мы можем вызвать new
непосредственно в классе, передавая ему информацию, которую мы хотим использовать для создания начального состояния объекта.
Book.new('Triumph of the City', 'David Glaeser', 338)
Мы создали или инстанциировали один объект Book - один экземпляр класса Book
. Этот book
имеет набор переменных экземпляра - @title
, @author
и @pages
. Переменные экземпляра этого конкретного экземпляра класса привязаны к значениям, которые мы передаем при вызове метода ::new
. Другими словами, этот Book
объект имеет уникальное состояние, состоящее из всех объектов, связанных с его переменными экземпляра.
Мы также можем присвоить этот объект локальной переменной для хранения.
triumph_of_the_city = Book.new('Triumph of the City', 'David Glaeser', 338)
Как мы можем получить доступ к данным, хранящимся в объекте? Как мы можем узнать количество страниц в нашем Book
объекте? Прямо сейчас - наши возможности ограничены. Если вы хотите получить более глубокие знания о метапрограммировании Ruby, изучите Object#instance_variables
и Object#instance_variable_get
.
Как правило, мы не можем напрямую получить доступ к переменным экземпляра объекта вне определения класса - они являются частными и доступны только в рамках объекта. Однако мы можем определить методы экземпляра в определении класса и ссылаться на переменные экземпляра там. Затем эти методы могут быть вызваны для объекта этого класса для доступа к переменным экземпляра этого объекта и взаимодействия с ними.
class Book def initialize(title, author, pages) @title = title @author = author @pages = pages end def title @title end end
Здесь мы добавили метод экземпляра title
, в котором мы получаем доступ к переменной экземпляра @title
. Поскольку этот метод доступен для всех Book
объектов, мы можем вызвать title
таким образом.
triumph_of_the_city.title
Это возвращает название книги.
Методы экземпляра, которые обращаются к переменным экземпляра, называются методами getter, а те, которые позволяют пользователю изменять переменные экземпляра, называются методами setter. Напишем еще несколько геттеров и сеттер-метод.
class Book def initialize(title, author, pages) @title = title @author = author @pages = pages end def title @title end def author @author end def pages @pages end def pages=(new_pages) @pages = new_pages end end
Как видите, теперь у нас есть методы получения для title
, author
и pages
. У нас также есть метод установки для pages
. Это может показаться странным. В Ruby принято использовать =
в конце имени метода установки. Это похоже на методы предиката, заканчивающиеся на ?
.
Установщик принимает один аргумент - новое значение, которое пользователь хочет присвоить переменной экземпляра. С синтаксическим сахаром Ruby вызвать метод установки для объекта за пределами определения класса очень просто! Допустим, мы вырываем одну из страниц из нашей книги.
triumph_of_the_city.pages = 337
Ruby позволяет заданию переменной экземпляра объекта выглядеть как присваивание. Без сахара использование метода установки выглядит так, как показано в приведенном ниже коде.
triumph_of_the_city.pages=(337)
К синтаксису может потребоваться некоторое время, чтобы привыкнуть к нему, но давайте подведем итоги всего, что мы сделали до сих пор.
- Мы определили класс. Этот класс является шаблоном для всех объектов, которые создаются из него. В классе мы диктуем возможные атрибуты и возможное поведение, которое будут иметь объекты, созданные из этого класса.
- Мы создали экземпляр объекта
Book
и дали ему несколько уникальных значений для переменных экземпляра. - Мы определили несколько методов получения для доступа к переменным экземпляра путем вызова метода для объектов вне определения класса.
- Мы определили метод установки, который можно использовать для повторного присвоения переменной экземпляра
pages
новому значению.
Давайте создадим еще один объект Book
и свяжем его с локальной переменной.
walkable_city = Book.new('Walkable City Rules', 'Jeff Speck', 290)
Теперь у нас есть два разных экземпляра класса Book
. У них разные названия, авторы и количество страниц. Другими словами, это два разных Book
объекта, каждый из которых имеет свое состояние.
* В этой статье мы не будем говорить о attr_*
методах - сокращенном варианте Ruby для создания геттеров и сеттеров.
Методы экземпляра дают функциональность
Прямо сейчас у нас есть два Book
объекта. Мы можем создать новую книгу, передав аргументы в вызов метода ::new
класса Book
. Давайте подумаем о функциях, доступных для любых Book
объектов.
Поскольку мы написали методы экземпляра получателя для title
, author
и pages
, мы можем получить доступ к значениям каждой из этих переменных экземпляра извне класса. Мы также написали метод установки для pages
, который позволяет нам изменять значение, привязанное к @pages
в любом Book
объекте, на любое желаемое значение. У нас есть доступ для чтения и записи для pages
переменной экземпляра, а у нас есть read-only
доступ к title
и author
.
С помощью нашего метода установки для @pages
мы можем изменить количество страниц. Фактически, пользователь нашей программы может даже установить количество страниц для объекта String
из 'hello'
.
walkable_city.pages = 'hello'
Присваивать String
переменной, которая предназначена для представления количества страниц в книге, бессмысленно. Давайте наложим некоторые ограничения на то, что пользователь может делать с переменной экземпляра @pages
. Вместо того, чтобы позволять пользователю устанавливать любое значение по своему усмотрению, давайте позволим ему выбрать только Integer
объект.
def pages=(new_pages) if new_pages.instance_of?(Integer) @pages = new_pages else puts 'Sorry, please enter a number' end end
Мы используем простое условие if-else
, которое вызывает Object#instance_of?
для объекта, привязанного к переданному в параметре new_pages
. Вызову instance_of?
мы передаем аргумент имени класса Integer
. Это метод предиката, который вернет true
или false
, в зависимости от того, соответствует ли класс вызывающего объекта переданному аргументу. Если new_pages
привязан к объектуInteger
, мы указываем переменной экземпляра @pages
на этот Integer
. Если пользователь пытается передать объект любого другого типа, например String
или Hash
, мы выводим сообщение об ошибке на терминал и не присваиваем значение @pages
.
Мы эффективно приняли одну защитную меру, чтобы гарантировать, что @pages
всегда будет указывать на Integer
объект. Это делает программу более надежной. Если у нас есть другой метод экземпляра, который использует @pages
, мы можем быть немного более уверены в том, что можем ожидать Integer
, а не объект из какого-то другого класса. Это убережет нашу программу от поломки.
Теперь, когда у нас есть небольшой, работающий Book
класс, пара Book
объектов с их собственными уникальными переменными экземпляра и общий набор методов (только методы получения и установки), мы можем взглянуть на основные идеи ООП и его основные преимущества. в качестве парадигмы программирования во второй статье этой серии здесь.