Как понять что код написан по принципам ООП?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как понять, что код написан по принципам ООП
Объектно-ориентированное программирование (ООП) — это парадигма, основанная на объектах и классах. Хороший ООП код имеет чёткие признаки, которые легко определить. Рассмотрим, на что смотреть.
1. Четыре столпа ООП
1.1 Инкапсуляция (Encapsulation)
Данные и методы упакованы вместе, скрыты детали реализации.
# ❌ Плохо — нет инкапсуляции
class BankAccount:
def __init__(self, balance):
self.balance = balance # Прямой доступ
# Кто угодно может менять баланс
account = BankAccount(1000)
account.balance = -5000 # Опа! Никакой защиты
# ✅ Хорошо — инкапсуляция
class BankAccount:
def __init__(self, balance: float):
self.__balance = balance # Приватный атрибут
@property
def balance(self) -> float:
return self.__balance
@balance.setter
def balance(self, value: float) -> None:
if value < 0:
raise ValueError("Баланс не может быть отрицательным")
self.__balance = value
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
self.__balance += amount
def withdraw(self, amount: float) -> None:
if amount > self.__balance:
raise ValueError("Недостаточно средств")
self.__balance -= amount
# Использование
account = BankAccount(1000)
account.deposit(500) # Правильно
account.balance = -5000 # ValueError! Защита работает
Признаки инкапсуляции:
- Приватные атрибуты (
__attr,_attr) - Публичные методы для доступа (@property, getters, setters)
- Валидация внутри методов
- Скрытые детали реализации
1.2 Наследование (Inheritance)
Классы наследуют функциональность от других классов, образуя иерархию.
# ✅ Хорошее наследование — правильная иерархия
class Animal:
"""Базовый класс"""
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
return "Generic sound"
class Dog(Animal):
"""Наследует от Animal"""
def speak(self) -> str:
return "Woof!"
class Cat(Animal):
def speak(self) -> str:
return "Meow!"
# Использование
dog = Dog("Rex")
cat = Cat("Whiskers")
print(dog.speak()) # Woof!
print(cat.speak()) # Meow!
# ✅ Множественное наследование (когда имеет смысл)
class Swimmer:
def swim(self) -> str:
return "Swimming..."
class Flyer:
def fly(self) -> str:
return "Flying..."
class Duck(Animal, Swimmer, Flyer):
"""Утка летает и плывает"""
def speak(self) -> str:
return "Quack!"
duck = Duck("Donald")
print(duck.speak()) # Quack!
print(duck.swim()) # Swimming...
print(duck.fly()) # Flying...
Признаки наследования:
- Класс наследует от другого класса (
class Child(Parent):) - Переопределение методов (override)
- Иерархия классов (parent → children)
- Использование
super()для вызова методов родителя
1.3 Полиморфизм (Polymorphism)
Один интерфейс, разные реализации. Объекты разных типов отвечают на одни и те же методы по-разному.
# ✅ Полиморфизм через наследование
class Shape:
"""Абстрактный класс"""
def area(self) -> float:
raise NotImplementedError("Подклассы должны реализовать это")
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class Triangle(Shape):
def __init__(self, base: float, height: float):
self.base = base
self.height = height
def area(self) -> float:
return 0.5 * self.base * self.height
# Полиморфное использование
shapes: list[Shape] = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 8)
]
# Один цикл, разные реализации
for shape in shapes:
print(f"Площадь: {shape.area():.2f}")
# Площадь: 78.54
# Площадь: 24.00
# Площадь: 12.00
# ✅ Полиморфизм через duck typing
class Logger:
def log(self, message: str) -> None:
print(f"[LOG] {message}")
class FileLogger:
def log(self, message: str) -> None:
with open("log.txt", "a") as f:
f.write(f"[LOG] {message}\n")
class DatabaseLogger:
def log(self, message: str) -> None:
# Сохраняем в БД
pass
def process_with_logging(logger: ?):
"""Работает с любым логгером, который имеет метод log()"""
logger.log("Start")
# ... работа ...
logger.log("Done")
# Все работают одинаково
process_with_logging(Logger())
process_with_logging(FileLogger())
process_with_logging(DatabaseLogger())
Признаки полиморфизма:
- Одинаковый интерфейс (одинаковые имена методов)
- Разные реализации в разных классах
- Использование базового класса или интерфейса для работы
- Один код работает с разными типами
1.4 Абстракция (Abstraction)
Скрываем сложность, предоставляем простой интерфейс.
from abc import ABC, abstractmethod
# ✅ Абстрактный класс
class Database(ABC):
"""Определяет интерфейс для любой БД"""
@abstractmethod
def connect(self) -> None:
pass
@abstractmethod
def query(self, sql: str) -> list:
pass
@abstractmethod
def close(self) -> None:
pass
class PostgreSQL(Database):
"""Реализация для PostgreSQL"""
def connect(self) -> None:
print("Подключение к PostgreSQL...")
def query(self, sql: str) -> list:
print(f"Выполнен запрос: {sql}")
return []
def close(self) -> None:
print("Отключение от PostgreSQL...")
class MySQL(Database):
"""Реализация для MySQL"""
def connect(self) -> None:
print("Подключение к MySQL...")
def query(self, sql: str) -> list:
print(f"Выполнен запрос в MySQL: {sql}")
return []
def close(self) -> None:
print("Отключение от MySQL...")
# Абстракция: не важно какая БД, интерфейс одинаковый
def get_users(db: Database) -> list:
db.connect()
users = db.query("SELECT * FROM users")
db.close()
return users
get_users(PostgreSQL())
get_users(MySQL())
Признаки абстракции:
- Абстрактные классы (ABC, @abstractmethod)
- Интерфейсы и контракты
- Скрытая сложность внутри методов
- Простой публичный API
2. SOLID принципы
2.1 Single Responsibility (S)
Одна ответственность на класс.
# ❌ Нарушение SRP
class UserManager:
def create_user(self, name: str, email: str) -> None:
# Создание пользователя
pass
def send_email(self, email: str, message: str) -> None:
# Отправка email — это не ответственность UserManager!
pass
def generate_pdf_report(self) -> None:
# Генерация отчёта — это не ответственность UserManager!
pass
# ✅ Соблюдение SRP
class UserRepository:
def create_user(self, name: str, email: str) -> User:
# Только создание пользователя
pass
class EmailService:
def send(self, email: str, message: str) -> None:
# Только отправка email
pass
class ReportGenerator:
def generate_pdf(self, data: list) -> bytes:
# Только генерация отчётов
pass
2.2 Open/Closed (O)
Открыто для расширения, закрыто для модификации.
# ❌ Нарушение OCP — нужно менять класс при добавлении типа
class OrderProcessor:
def process(self, order):
if order.type == "digital":
# обработка цифрового товара
pass
elif order.type == "physical":
# обработка физического товара
pass
elif order.type == "service":
# обработка услуги
pass
# Каждый новый тип требует изменения класса!
# ✅ Соблюдение OCP
from abc import ABC, abstractmethod
class OrderHandler(ABC):
@abstractmethod
def process(self, order):
pass
class DigitalOrderHandler(OrderHandler):
def process(self, order):
pass
class PhysicalOrderHandler(OrderHandler):
def process(self, order):
pass
class ServiceOrderHandler(OrderHandler):
def process(self, order):
pass
class OrderProcessor:
def __init__(self, handlers: dict[str, OrderHandler]):
self.handlers = handlers
def process(self, order):
handler = self.handlers[order.type]
handler.process(order)
# Добавить новый тип просто: новый класс + регистрация!
2.3 Liskov Substitution (L)
Подклассы должны быть заменяемы вместо базовых классов.
# ❌ Нарушение LSP
class Bird:
def fly(self) -> str:
return "Flying..."
class Penguin(Bird):
def fly(self) -> str:
raise Exception("Пингвин не может летать!") # Нарушение!
# ✅ Соблюдение LSP
class Bird:
pass
class FlyingBird(Bird):
def fly(self) -> str:
return "Flying..."
class Penguin(Bird):
def swim(self) -> str:
return "Swimming..."
# Теперь всё логично
def make_bird_fly(bird: FlyingBird):
return bird.fly()
2.4 Interface Segregation (I)
Много узких интерфейсов лучше одного толстого.
# ❌ Толстый интерфейс
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
@abstractmethod
def manage(self):
pass
class Robot(Worker):
def work(self):
pass
def eat(self):
pass # Robot не может есть!
def manage(self):
pass # Robot не может управлять!
# ✅ Узкие интерфейсы
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class Manageable(ABC):
@abstractmethod
def manage(self):
pass
class Human(Workable, Eatable, Manageable):
def work(self):
pass
def eat(self):
pass
def manage(self):
pass
class Robot(Workable):
def work(self):
pass
# Только то, что нужно!
2.5 Dependency Inversion (D)
Зависимости от абстракций, не от конкретных реализаций.
# ❌ Зависимость от конкретного класса
class EmailSender:
def send(self, to: str, message: str):
pass
class UserService:
def __init__(self):
self.email_sender = EmailSender() # Зависимость от конкретного класса
def register(self, email: str):
# регистрация
self.email_sender.send(email, "Welcome!")
# ✅ Зависимость от интерфейса
from abc import ABC, abstractmethod
class MessageSender(ABC):
@abstractmethod
def send(self, to: str, message: str):
pass
class EmailSender(MessageSender):
def send(self, to: str, message: str):
pass
class SMSSender(MessageSender):
def send(self, to: str, message: str):
pass
class UserService:
def __init__(self, message_sender: MessageSender):
self.message_sender = message_sender # Зависимость от интерфейса
def register(self, contact: str):
self.message_sender.send(contact, "Welcome!")
# Используется Email или SMS — не важно
service = UserService(EmailSender())
service.register("user@example.com")
service = UserService(SMSSender())
service.register("+79991234567")
3. Признаки хорошего ООП кода
Чеклист:
- Есть классы и объекты, не просто функции
- Использована инкапсуляция (приватные атрибуты, методы доступа)
- Есть иерархия классов (наследование)
- Один интерфейс, разные реализации (полиморфизм)
- Каждый класс имеет одну ответственность (SRP)
- Код открыт для расширения, закрыт для модификации (OCP)
- Нет нарушений принципа подстановки Лискова (LSP)
- Узкие интерфейсы вместо толстых (ISP)
- Зависимости от абстракций (DIP)
- Используются дизайн-паттерны (Strategy, Factory, Observer и т.д.)
- Код читаем и тестируем
- Нет дублирования (DRY)
4. Пример хорошего ООП кода
from abc import ABC, abstractmethod
from typing import List
from dataclasses import dataclass
from datetime import datetime
# Доменные модели
@dataclass
class User:
id: int
email: str
name: str
@dataclass
class Order:
id: int
user_id: int
items: List[str]
total_price: float
created_at: datetime
# Абстракции
class Repository(ABC):
@abstractmethod
def save(self, entity):
pass
@abstractmethod
def find_by_id(self, id: int):
pass
class NotificationService(ABC):
@abstractmethod
def notify(self, user: User, message: str):
pass
# Конкретные реализации
class UserRepository(Repository):
def save(self, user: User):
print(f"Сохранён пользователь: {user.email}")
def find_by_id(self, id: int) -> User:
return User(id, "user@example.com", "User")
class EmailNotificationService(NotificationService):
def notify(self, user: User, message: str):
print(f"Email отправлено {user.email}: {message}")
class SMSNotificationService(NotificationService):
def notify(self, user: User, message: str):
print(f"SMS отправлено на номер пользователя: {message}")
# Бизнес-логика (Use Case)
class RegisterUserUseCase:
def __init__(self, user_repo: Repository, notifier: NotificationService):
self.user_repo = user_repo
self.notifier = notifier
def execute(self, email: str, name: str) -> User:
user = User(id=1, email=email, name=name)
self.user_repo.save(user)
self.notifier.notify(user, "Welcome!")
return user
# Использование
user_repo = UserRepository()
notifier = EmailNotificationService()
use_case = RegisterUserUseCase(user_repo, notifier)
user = use_case.execute("john@example.com", "John")
# Можем легко переключиться на SMS
notifier_sms = SMSNotificationService()
use_case_sms = RegisterUserUseCase(user_repo, notifier_sms)
Итоговый тест
Если ты видишь в коде:
- Классы с состоянием и поведением — инкапсуляция
- Иерархия классов с переопределением методов — наследование
- Один код работает с разными типами — полиморфизм
- Абстрактные классы и интерфейсы — абстракция
- Соблюдение SOLID принципов — правильная архитектура
То это ООП код!