ООП

ООП

Nван Makul

1 часть

Объектно-ориентированное программирование: на пальцах

2 часть

Что такое классы в объектно-ориентированном программировании

3 часть

Объясняем объекты

4 часть

Классы и функции

5 часть

ООП: атрибуты и методы

6 часть

Зачем нужны абстракции и интерфейсы

Объектно-ориентированное программирование: на пальцах

Статья не мальчика, но мужа.

Настало время серьёзных тем: сегодня расскажем про объектно-ориентированное программирование, или ООП. Это тема для продвинутого уровня разработки, и мы хотим, чтобы вы его постигли.

Из этого термина можно сделать вывод, что ООП — это такой подход к программированию, где на первом месте стоят объекты. На самом деле там всё немного сложнее, но мы до этого ещё доберёмся. Для начала поговорим про ООП вообще и разберём, с чего оно начинается.

Обычное программирование (процедурное)

Чаще всего под обычным понимают процедурное программирование, в основе которого — процедуры и функции. Функция — это мини-программа, которая получает на вход какие-то данные, что-то делает внутри себя и может отдавать какие-то данные в результате вычислений. Представьте, что это такой конвейер, который упакован в коробочку.

Например, в интернет-магазине может быть функция «Проверить email». Она получает на вход какой-то текст, сопоставляет со своими правилами и выдаёт ответ: это правильный электронный адрес или нет. Если правильный, то true, если нет — то false.

Функции полезны, когда нужно упаковать много команд в одну. Например, проверка электронного адреса может состоять из одной проверки на регулярные выражения, а может содержать множество команд: запросы в словари, проверку по базам спамеров и даже сопоставление с уже известными электронными адресами. В функцию можно упаковать любой комбайн из действий и потом просто вызывать их все одним движением.

Что не так с процедурным программированием

Процедурное программирование идеально работает в простых программах, где все задачи можно решить, грубо говоря, десятком функций. Функции аккуратно вложены друг в друга, взаимодействуют друг с другом, можно передать данные из одной функции в другую.

Например, вы пишете функцию «Зарегистрировать пользователя интернет-магазина». Внутри неё вам нужно проверить его электронный адрес. Вы вызываете функцию «Проверить email» внутри функции «Зарегистрировать пользователя», и в зависимости от ответа функции вы либо регистрируете пользователя, либо выводите ошибку. И у вас эта функция встречается ещё в десяти местах. Функции как бы переплетены.

Тут приходит продакт-менеджер и говорит: «Хочу, чтобы пользователь точно знал, в чём ошибка при вводе электронного адреса». Теперь вам нужно научить функцию выдавать не просто true — false, а ещё и код ошибки: например, если в адресе опечатка, то код 01, если адрес спамерский — код 02 и так далее. Это несложно реализовать.

Вы залезаете внутрь этой функции и меняете её поведение: теперь она вместо true — false выдаёт код ошибки, а если ошибки нет — пишет «ОК».

И тут ваш код ломается: все десять мест, которые ожидали от проверяльщика true или false, теперь получают «ОК» и из-за этого ломаются.

Теперь вам нужно:

  • либо переписывать все функции, чтобы научить их понимать новые ответы проверяльщика адресов;
  • либо переделать сам проверяльщик адресов, чтобы он остался совместимым со старыми местами, но в нужном вам месте как-то ещё выдавал коды ошибок;
  • либо написать новый проверяльщик, который выдаёт коды ошибок, а в старых местах использовать старый проверяльщик.

Задача, конечно, решаемая за час-другой.

Но теперь представьте, что у вас этих функций — сотни. И изменений в них нужно делать десятки в день. И каждое изменение, как правило, заставляет функции вести себя более сложным образом и выдавать более сложный результат. И каждое изменение в одном месте ломает три других места. В итоге у вас будут нарождаться десятки клонированных функций, в которых вы сначала будете разбираться, а потом уже нет.

Это называется спагетти-код, и для борьбы с ним как раз придумали объектно-ориентированное программирование.

Убираем спагетти-код


Объектно-ориентированное программирование

Основная задача ООП — сделать сложный код проще. Для этого программу разбивают на независимые блоки, которые мы называем объектами.

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

Программисты договорились, что данные внутри объекта будут называться свойствами, а функции — методами. Но это просто слова, по сути это те же переменные и функции.

Объект можно представить как независимый электроприбор у вас на кухне. Чайник кипятит воду, плита греет, блендер взбивает, мясорубка делает фарш. Внутри каждого устройства куча всего: моторы, контроллеры, кнопки, пружины, предохранители — но вы о них не думаете. Вы нажимаете кнопки на панели каждого прибора, и он делает то, что от него ожидается. И благодаря совместной работе этих приборов у вас получается ужин.

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

Инкапсуляция, абстракция, наследование, полиморфизм

Инкапсуляция — объект независим: каждый объект устроен так, что нужные для него данные живут внутри этого объекта, а не где-то снаружи в программе. Например, если у меня есть объект «Пользователь», то у меня в нём будут все данные о пользователе: и имя, и адрес, и всё остальное. И в нём же будут методы «Проверить адрес» или «Подписать на рассылку».

Абстракция — у объекта есть «интерфейс»: у объекта есть методы и свойства, к которым мы можем обратиться извне этого объекта. Так же, как мы можем нажать кнопку на блендере. У блендера есть много всего внутри, что заставляет его работать, но на главной панели есть только кнопка. Вот эта кнопка и есть абстрактный интерфейс.

В программе мы можем сказать: «Удалить пользователя». На языке ООП это будет «пользователь.удалить ()» — то есть мы обращаемся к объекту «пользователь» и вызываем метод «удалить». Кайф в том, что нам не так важно, как именно будет происходить удаление: ООП позволяет нам не думать об этом в момент обращения.

Например, над магазином работают два программиста: один пишет модуль заказа, а второй — модуль доставки. У первого в объекте «заказ» есть метод «отменить». И вот второму нужно из-за доставки отменить заказ. И он спокойно пишет: «заказ.отменить ()». Ему неважно, как другой программист будет реализовывать отмену: какие он отправит письма, что запишет в базу данных, какие выведет предупреждения.

Объектно-ориентированное программирование: на пальцах


Наследование — способность к копированию. ООП позволяет создавать много объектов по образу и подобию другого объекта. Это позволяет не копипастить код по двести раз, а один раз нормально написать и потом много раз использовать.

Например, у вас может быть некий идеальный объект «Пользователь»: в нём вы прописываете всё, что может происходить с пользователем. У вас могут быть свойства: имя, возраст, адрес, номер карты. И могут быть методы «Дать скидку», «Проверить заказ», «Найти заказы», «Позвонить».

На основе этого идеального пользователя вы можете создать реального «Покупателя Ивана». У него при создании будут все свойства и методы, которые вы задали у идеального покупателя, плюс могут быть какие-то свои, если захотите.

Идеальные объекты программисты называют классами.

Полиморфизм — единый язык общения. В ООП важно, чтобы все объекты общались друг с другом на понятном им языке. И если у разных объектов есть метод «Удалить», то он должен делать именно это и писаться везде одинаково. Нельзя, чтобы у одного объекта это было «Удалить», а у другого «Стереть».

При этом внутри объекта методы могут быть реализованы по-разному. Например, удалить товар — это выдать предупреждение, а потом пометить товар в базе данных как удалённый. А удалить пользователя — это отменить его покупки, отписать от рассылки и заархивировать историю его покупок. События разные, но для программиста это неважно. У него просто есть метод «Удалить ()», и он ему доверяет.

Объектно-ориентированное программирование: на пальцах


Такой подход позволяет программировать каждый модуль независимо от остальных. Главное — заранее продумать, как модули будут общаться друг с другом и по каким правилам. При таком подходе вы можете улучшить работу одного модуля, не затрагивая остальные — для всей программы неважно, что внутри каждого блока, если правила работы с ним остались прежними.

Плюсы и минусы ООП

У объектно-ориентированного программирования много плюсов, и именно поэтому этот подход использует большинство современных программистов.

  1. Визуально код становится проще, и его легче читать. Когда всё разбито на объекты и у них есть понятный набор правил, можно сразу понять, за что отвечает каждый объект и из чего он состоит.
  2. Меньше одинакового кода. Если в обычном программировании одна функция считает повторяющиеся символы в одномерном массиве, а другая — в двумерном, то у них большая часть кода будет одинаковой. В ООП это решается наследованием.
  3. Сложные программы пишутся проще. Каждую большую программу можно разложить на несколько блоков, сделать им минимальное наполнение, а потом раз за разом подробно наполнить каждый блок.
  4. Увеличивается скорость написания. На старте можно быстро создать нужные компоненты внутри программы, чтобы получить минимально работающий прототип.

А теперь про минусы:

  1. Сложно понять и начать работать. Подход ООП намного сложнее обычного процедурного программирования — нужно знать много теории, прежде чем будет написана хоть одна строчка кода.
  2. Требует больше памяти. Объекты в ООП состоят из данных, интерфейсов, методов и много другого, а это занимает намного больше памяти, чем простая переменная.
  3. Иногда производительность кода будет ниже. Из-за особенностей подхода часть вещей может быть реализована сложнее, чем могла бы быть. Поэтому бывает такое, что ООП-программа работает медленнее, чем процедурная (хотя с современными мощностями процессоров это мало кого волнует).

Что такое классы в объектно-ориентированном программировании

Глубокое погружение в самую сложную и неинтуитивную область программирования.

В этом цикле статей мы говорим об объектно-ориентированном программировании — передовом и очень распространённом подходе к разработке. Это стоит знать всем, кто серьёзно относится к программированию и хочет зарабатывать в этой области.

Если не читали предыдущую статью, вот краткое содержание:

Кратко: что мы уже знаем из прошлой статьи

  • ООП — это подход к программированию. Такой набор практик и принципов, которыми пользуются хорошие разработчики. Противопоставление этому подходу — традиционное процедурное программирование.
  • В процедурном программировании мы пишем функции, которые выполняют какие-то задачи. И при необходимости вызываем одни функции из других. В программе функции живут отдельно, данные — отдельно.
  • Главная проблема процедурного программирования — сложно писать и поддерживать большие проекты. Любой мало-мальски сложный продукт будет требовать сотен функций, которые будут связаны между собой. Получится «спагетти-код».
  • В ООП функции и данные группируются в объекты. Объекты более-менее независимые и общаются друг с другом по строго определённым правилам.
  • Данные в ООП хранятся внутри объектов и называются свойствами объектов. Например, у объекта user может быть свойство name со значением ‘Иван’.
  • Функции в ООП тоже хранятся внутри объектов и называются методами объектов. Например, у объекта user может быть метод sendEmail(), который отправляет этому юзеру письмо.
  • Можно представить, что в ООП взяли «спагетти-код» с тефтелями и разложили из огромного чана порционно по контейнерам. Теперь в каждом контейнере есть спагетти и тефтели, и каждый программист может работать над своим контейнером-объектом, а не ковыряться в общем чане со спагетти.


Одно из преимуществ ООП — не нужно много раз писать один и тот же код. Можно однажды придумать какую-то красивую штуку и потом заново её использовать буквально одной строкой. Для этого и нужны классы.

Что за классы

Вот одно из формальных определений класса: «Класс — это элемент ПО, описывающий абстрактный тип данных и его частичную или полную реализацию»

Если более по-русски, то класс — это шаблон кода, по которому создаётся какой-то объект. Это как рецепт приготовления блюда или инструкция по сборке мебели: сам по себе класс ничего не делает, но с его помощью можно создать новый объект и уже его использовать в работе.

Если пока непонятно, погружайтесь в пример:

Сила примера

Призовём на помощь силу примеров и поговорим про сотовые телефоны.

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

Но одного железа недостаточно — нужно соединить его между собой так, чтобы всё работало без сбоёв. Кроме этого, нужно предусмотреть, что происходит при нажатии на кнопки, что выводится на экран и как пользователь будет управлять этим телефоном.

Следующий этап — описать каждую деталь, из которой состоит телефон, каждую микросхему и плату, и объяснить, как детали работают друг с другом. Последний шаг — написать руководство пользователя, где будет полностью рассказано, что можно делать с телефоном, как запустить звонилку или отправить смс.

Мы только что сделали новый класс для телефона — полный набор нужных знаний, описаний, свойств и инструкций, который описывает нашу модель. Все эти инструкции и описания — это ещё не телефон, но из них этот телефон можно сделать.

В программировании у класса есть наборы данных — в нашем случае это комплектующие для телефона. Ещё есть функции для работы с классами, которые называются методами — это то, как пользователь будет работать с нашим телефоном, что он будет на нём делать и каким образом.

Классы на практике

Все примеры дальше мы будем делать на Python, потому что это стильно, модно и молодёжно. А сам Python — очень объектно-ориентированный язык, почти всё в нём — это объекты. Вот и опробуем.



Как установить Python на компьютер и начать на нём писать


Допустим, мы пишем интернет-магазин с системой скидок. Нам нужно работать с пользователями — постоянными покупателями. Пользователь у нас будет объектом: у него будет имя, возраст и адрес доставки по умолчанию. Мы заведём класс, который поможет нам инициировать нового покупателя.

class  User:
    """Класс для всех покупателей""" 
    user_count = 0    
    def __init__(name, age, adress): 
        self.name = name 
        self.age = age
        self.adress = adress 
        user.user_count += 1  

Python

Copy

Здесь сказано: «Вот класс для покупателя. У него есть три свойства: имя, возраст и адрес». Теперь мы можем заводить новых покупателей одной строкой:

# Создаём первого покупателя

user1 = User ('Вася', 23,'Чебоксары')

# Создаём второго покупателя

user2 = User ('Маша', 19,'Белгород')


Объясняем объекты

Главное понятие современных подходов к программированию.

Мы тут постепенно раскладываем по полочкам объектно-ориентированное программирование. В первой части мы говорили про спагетти-код и предназначение ООП. Дальше разбирали классы — то, из чего делаются объекты. Теперь, наконец-то, сами объекты.

На примере Айфона

Вот лаборатория, конструкторское бюро. Там у нас сидят дизайнеры, инженеры и программисты. Они сделали чертежи и инструкции, как собирать новые Айфоны. Если все эти инструкции собрать, их совокупность можно назвать классом. Это такая «идея Айфона».

Из предыдущей статьи мы знаем, что в классе мы можем прописать свойства и методы.

Свойства — это, грубо говоря, данные. У Айфона может быть «свойство» в виде записанной на него операционной системы.

Методы — это, грубо говоря, «что это устройство может делать». У Айфона могут быть «методы» типа позвонить, отправить смс, запустить камеру, сделать снимок.

Всё это множество свойств и методов составляет общий класс, который мы потом используем для создания нашего воображаемого Айфона.

Когда прописан класс, завод может взять его (то есть все эти чертежи и спецификации) и сделать много копий айфонов. Каждый телефон, который выходит с завода, имеет чётко прописанные характеристики: размер памяти, диагональ экрана, цвет корпуса и так далее. Это всё было заранее продумано и записано в инструкциях по изготовлению. При этом все телефоны получаются одновременно одинаковые и разные.

Одинаковые: все телефоны одной модели не отличаются почти ничем. У них одинаковое железо, характеристики и возможности. Если взять с конвейера два случайных телефона, между ними не будет никакой разницы. Все они работают одинаково — так, как задумали проектировщики.

Разные: при этом у каждого айфона есть уникальный серийный номер и другие уникальные идентификаторы. Какие-то модели могут отличаться цветом корпуса или иметь слот на 2 сим-карты, как версии для китайского рынка.

После того как айфоны выходят с завода и попадают в руки людям, каждый телефон начинает наполняться разными данными: фото, видео, музыка, книги и программы. При обычном использовании в мире нет двух телефонов с полностью одинаковыми данными внутри. Но при этом у всех одинаково запускается камера, одинаковым способом меняются обои и отправляются смс. Это означает, что все свойства и методы работы с данной моделью одинаковы для всех телефонов этой модели. Достаточно понять, как работать с одним, — и вы понимаете, как работать со всеми точно такими же.

Вся эта аналогия с Айфонами нужна, чтобы понять смысл объектов:

  • Есть класс — чертёж будущего объекта.
  • На основе этого класса можно сделать объект.
  • Созданный объект имеет всё, что в него вложено из класса.
  • После создания с объектом можно делать всё, что в нём было заложено классом.
  • Ещё с объектом можно делать всё остальное, что позволяет язык программирования.

Как создаются объекты

В общем виде на JavaScript объект задаётся вот такой конструкцией. Пока что она абстрактная, просто пример:

class MyClass
prop1 = value1; // свойства класса
prop2 = value2;
constructor(...) { // команда-конструктор, с её помощью создаются новые объекты
 // содержимое конструктора
  }
method1(...) {} // методы класса, то, что класс умеет делать, и как с ним работать
method2(...) {} 
// ...
}

Теперь сделаем конкретный класс, который отвечает за телефон:

class iPhone{
constructor(memory,color) { // при создании будем указывать
размер встроенной памяти и цвет
this.memory = memory; // создаём телефон с заданной памятью…
this.color = color;  // … и с заданным цветом
whois() { // метод, который выводит информацию о телефоне
console.log('Память: ' + this.memory + 'Гб. Цвет: ' +  this.color);
  }
    }
}

У нас появилась команда this, которая отвечает за свойства конкретного класса. С её помощью класс обращается внутри себя к нужным значениям. Ещё мы сделали метод whois, который выводит значение памяти и цвет. Каждый объект, который мы создадим на основе этого класса, будет уметь это делать — для этого достаточно написать имя объекта, поставить точку, а потом написать имя метода.

Нам осталось только создать новый объект и проверить, что за данные у него получились внутри и как работает метод:

iPhone_object = new iPhone(64,'Black'); // создаём новый объект
— айфон с определённой памятью и цветом
iPhone_object.whois(); // проверяем, как работает
метод класса в готовом объекте

В других языках принцип создания объектов практически такой же: делаем класс, в нём описываем всё, что нужно, а потом создаём объект на основе этого класса.



Report Page