Тема 5. OOP Concept
ООП это способ управлять сложностью программы, а не просто набор терминов. Когда проект растет, код без границ ответственности становится хрупким: любое изменение задевает соседние части. Принципы ООП нужны, чтобы разделить ответственность, стабилизировать контракты между частями системы и сделать код расширяемым.
Как смотреть на ООП на практике
Вместо вопроса «что такое ООП» полезнее задавать себе 3 практических вопроса:
- Где хранится состояние (данные) и кто имеет право его менять.
- Через какие публичные методы другие части системы взаимодействуют с объектом.
- Какие внутренние детали можно менять без влияния на внешний код.
Если на эти вопросы есть четкие ответы, архитектура обычно здорова.
Инкапсуляция
Инкапсуляция ограничивает прямой доступ к полям и заставляет менять состояние только через методы с проверками.
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("amount must be > 0");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0 || amount > balance) {
throw new IllegalArgumentException("invalid withdraw amount");
}
balance -= amount;
}
public double getBalance() {
return balance;
}
}
Что это дает:
- объект нельзя сломать случайной записью в поле;
- бизнес-правила централизованы;
- легче тестировать корректность поведения.
Наследование
Наследование выражает отношение is-a: дочерний класс расширяет базовый и переиспользует его контракт/поведение.
class Animal {
void speak() {
System.out.println("...");
}
}
class Dog extends Animal {
@Override
void speak() {
System.out.println("Woof");
}
}
Когда использовать:
- есть стабильная и естественная иерархия;
- общий контракт действительно общий;
- замена потомка на базовый тип логически корректна.
Когда осторожно:
- если нужно только переиспользовать код, часто лучше композиция (
has-a), потому что она гибче и проще для изменений.
Полиморфизм
Полиморфизм позволяет писать код к интерфейсу, а реализацию подставлять позже.
interface PaymentProcessor {
void pay(double amount);
}
class CardProcessor implements PaymentProcessor {
public void pay(double amount) {
System.out.println("Card: " + amount);
}
}
class PaymentService {
private final PaymentProcessor processor;
PaymentService(PaymentProcessor processor) {
this.processor = processor;
}
void process(double amount) {
processor.pay(amount);
}
}
Что это дает:
- расширяемость без переписывания сервиса;
- удобная подмена реализаций в тестах;
- меньшая связность между компонентами.
Абстракция
Абстракция скрывает внутреннюю сложность и оставляет снаружи только нужный контракт.
Пример: пользователь класса вызывает send(message), но не знает, как внутри устроены ретраи, сериализация, сеть и логирование.
Инженерный эффект абстракции:
- наружный код работает с понятным API;
- внутреннюю реализацию можно менять безопасно;
- снижается когнитивная нагрузка: каждому слою видна только своя часть сложности.
Как четыре принципа работают вместе
- Инкапсуляция защищает состояние.
- Абстракция стабилизирует внешний API.
- Полиморфизм делает систему расширяемой.
- Наследование или композиция переиспользуют поведение.
Итог: ООП это не «красивый стиль», а практический способ сделать код предсказуемым в развитии и безопасным для изменений.