ООП Общая теория
Содержание
- Python и объектно-ориентированный подход
- Что такое ООП
- Класс и объект
- Основные принципы ООП
- Когда ООП действительно полезно
- Когда ООП может быть лишним
- Частые ошибки
- Практика
Python и объектно-ориентированный подход
Python поддерживает несколько стилей программирования, включая объектно-ориентированное программирование. В Python почти все является объектом: числа, строки, списки, функции, классы и модули.
Это значит, что у значений есть не только данные, но и поведение.
text = "python"
print(text.upper())
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)text - объект строки, у которого есть метод upper(). numbers - объект списка, у которого есть метод append().
Что такое ООП
Объектно-ориентированное программирование - это подход, при котором программа строится вокруг объектов. Объект объединяет данные и действия, которые с этими данными связаны.
Кратко:
- класс описывает шаблон;
- объект создается по этому шаблону;
- атрибуты хранят состояние объекта;
- методы описывают поведение объекта.
ООП помогает группировать связанную логику и данные, чтобы программа была понятнее, расширяемее и удобнее в поддержке.
Класс и объект
Класс можно представить как инструкцию или чертеж. Объект - как конкретный экземпляр, созданный по этой инструкции.
class Course:
def __init__(self, title, duration_hours):
self.title = title
self.duration_hours = duration_hours
def describe(self):
return f"Курс {self.title}: {self.duration_hours} часов"
python_course = Course("Python для AQA", 48)
api_course = Course("API testing", 24)
print(python_course.describe())
print(api_course.describe())Оба объекта созданы по одному классу Course, но хранят разные данные.
Основные принципы ООП
Инкапсуляция
Инкапсуляция - это объединение данных и методов внутри объекта, а также контроль доступа к внутреннему состоянию.
Идея: внешний код не должен случайно ломать внутренние данные объекта. Для этого данные можно скрыть и менять через методы или свойства. Инструменты для этого — защищённые атрибуты и @property — разберём в 03.06 - Методы классов.
class Product:
def __init__(self, name, price):
self.name = name
self.__price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("Цена не может быть отрицательной")
self.__price = value
product = Product("Монитор", 18000)
product.price = 17000
print(product.price)Внешний код работает с price, но проверка находится внутри класса.
Наследование
Наследование позволяет создать новый класс на основе существующего. Общая логика остается в родителе, а дочерний класс добавляет или меняет детали. Подробно разберём это в 03.05 - Наследование.
class User:
def __init__(self, name):
self.name = name
def login(self):
return f"{self.name} вошел в систему"
class Admin(User):
def delete_record(self):
return "Запись удалена"
admin = Admin("Ирина")
print(admin.login())
print(admin.delete_record())Admin получил метод login() от User и добавил свое действие.
Полиморфизм
Полиморфизм позволяет работать с разными объектами через одинаковый интерфейс. Метод называется одинаково, но реализация может отличаться.
class EmailSender:
def send(self, message):
return f"Email отправлен: {message}"
class SmsSender:
def send(self, message):
return f"SMS отправлено: {message}"
def notify(sender, message):
print(sender.send(message))
notify(EmailSender(), "Отчет готов")
notify(SmsSender(), "Код подтверждения: 4821")Функция notify не знает деталей классов. Ей важно только, что у объекта есть метод send.
Абстракция
Абстракция скрывает детали реализации и оставляет только важный интерфейс. Пользователь объекта должен понимать, что можно сделать, но не обязан знать, как это устроено внутри.
В Python для строгой абстракции можно использовать модуль abc.
from abc import ABC, abstractmethod
class Report(ABC):
@abstractmethod
def generate(self):
pass
class PdfReport(Report):
def generate(self):
return "PDF-отчет создан"
class CsvReport(Report):
def generate(self):
return "CSV-отчет создан"Если класс наследуется от Report, он обязан реализовать метод generate().
Когда ООП действительно полезно
ООП хорошо подходит, когда в проекте есть много связанных сущностей и правил.
Примеры:
- интернет-магазин:
Product,Cart,Order,User; - система обучения:
Student,Course,Lesson,Progress; - банковская система:
Account,Transaction,Client; - тестовый фреймворк:
ApiClient,BasePage,TestUser,ReportBuilder.
ООП помогает:
- разделить проект на понятные части;
- переиспользовать код;
- расширять систему без переписывания всего проекта;
- проще тестировать отдельные компоненты;
- распределять работу между разработчиками.
Пример простой структуры магазина:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
class Cart:
def __init__(self):
self.items = []
def add_product(self, product, quantity):
self.items.append({"product": product, "quantity": quantity})
def calculate_total(self):
total = 0
for item in self.items:
total += item["product"].price * item["quantity"]
return total
class Order:
def __init__(self, cart):
self.cart = cart
self.status = "new"
def complete(self):
self.status = "completed"
return f"Заказ оформлен. Сумма: {self.cart.calculate_total()} руб."
keyboard = Product("Клавиатура", 3500)
monitor = Product("Монитор", 18000)
cart = Cart()
cart.add_product(keyboard, 2)
cart.add_product(monitor, 1)
order = Order(cart)
print(order.complete())Когда ООП может быть лишним
ООП не нужно использовать везде. Для маленьких задач классы могут только усложнить код.
ООП может быть избыточным, если:
- нужно один раз обработать файл;
- задача решается двумя-тремя функциями;
- нет устойчивых сущностей с состоянием;
- код не планируется расширять;
- класс создается только ради одной функции.
Пример лишнего класса:
class MathOperations:
def add(self, a, b):
return a + b
math = MathOperations()
print(math.add(2, 3))Проще так:
def add(a, b):
return a + b
print(add(2, 3))Главный критерий: ООП должно упрощать структуру, а не создавать лишнюю оболочку.
Частые ошибки
Избыточное использование классов
Ошибка: создавать класс для задачи, где достаточно функции.
Решение: сначала оценить, есть ли у сущности состояние, поведение и перспектива расширения.
Неправильное наследование
Ошибка: делать дочерний класс там, где между сущностями нет отношения является разновидностью.
class Document:
pass
class Printer(Document):
pass # логически неверно: принтер не является документомЛучше отделить сущности и передавать документ в методы принтера.
class Document:
pass
class Printer:
def print_document(self, document):
print("Печать документа")Отсутствие инкапсуляции
Ошибка: позволить внешнему коду менять важные данные как угодно.
class Account:
def __init__(self):
self.balance = 0
account = Account()
account.balance = -500Лучше контролировать изменение через метод или setter.
class Account:
def __init__(self):
self.__balance = 0
def deposit(self, amount):
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
self.__balance += amount
def get_balance(self):
return self.__balanceСлишком большие классы
Если класс делает все сразу, его трудно читать и тестировать. Лучше разделять ответственность: один класс - одна понятная роль.
Практика
-
Задание 1: определить сущности
Для системы онлайн-курсов предложите классы и их ответственность.
Минимальный набор:
Student;Course;Lesson;ProgressTracker.
Для каждого класса укажите 2-3 атрибута и 1-2 метода.
-
Задание 2: инкапсуляция
Создайте класс
Wallet.Требования:
- приватный атрибут
__balance; - метод
deposit(amount); - метод
withdraw(amount); - метод
get_balance(); - нельзя пополнять или снимать отрицательные суммы;
- нельзя снять больше, чем есть на балансе.
- приватный атрибут
-
Задание 3: наследование
Создайте класс
BaseUserс атрибутомnameи методомget_info(). Создайте классыStudentUserиMentorUser, которые наследуются отBaseUserи добавляют свои методы.Проверьте, что дочерние объекты используют общий метод родителя.
-
Задание 4: полиморфизм
Создайте классы
PdfExporter,CsvExporter,JsonExporter. У каждого должен быть методexport(data). Напишите функциюrun_export(exporter, data), которая вызываетexportу переданного объекта. -
Задание 5: абстракция
Создайте абстрактный класс
PaymentMethodс методомpay(amount). Затем создайте классыCardPaymentиCashPayment, которые реализуют этот метод.Проверьте, что нельзя создать объект абстрактного класса напрямую.
Короткий итог
ООП нужно не ради самих классов, а ради структуры. Классы помогают объединять данные и поведение, наследование уменьшает повторение, полиморфизм дает единый интерфейс для разных объектов, инкапсуляция защищает состояние, а абстракция прячет лишние детали. Хороший ООП-код делает проект понятнее, а не тяжелее.
⬅️ Назад: 03.02 - Исключения и обработка ошибок в Python | Далее: 03.04 - ООП Классы ➡️ Модуль: 03 - MOC