← Назад к вопросам

Как понять что код написан по принципам ООП?

2.2 Middle🔥 181 комментариев
#Python Core#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как понять, что код написан по принципам ООП

Объектно-ориентированное программирование (ООП) — это парадигма, основанная на объектах и классах. Хороший ООП код имеет чёткие признаки, которые легко определить. Рассмотрим, на что смотреть.

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)

Итоговый тест

Если ты видишь в коде:

  1. Классы с состоянием и поведением — инкапсуляция
  2. Иерархия классов с переопределением методов — наследование
  3. Один код работает с разными типами — полиморфизм
  4. Абстрактные классы и интерфейсы — абстракция
  5. Соблюдение SOLID принципов — правильная архитектура

То это ООП код!