Принципы ООП: четыре столпа и зачем они нужны

Принципы ООП: четыре столпа и зачем они нужны Полезное

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

Принципы эти язык-агностичны: они одинаково работают в Java, C#, Python, C++ и других языках, меняется только синтаксис. Ниже разберем каждый столп, покажем минимальный код на Java и разведем пары понятий, которые путают чаще всего: класс и объект, инкапсуляцию и абстракцию, наследование и полиморфизм.

Класс и объект: в чем разница

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

class Cat {                 // класс - чертеж
    String name;            // поле (атрибут)
    void meow() {           // метод (поведение)
        System.out.println(name + ": мяу");
    }
}

Cat murka = new Cat();      // объект - конкретный экземпляр
murka.name = "Мурка";
murka.meow();               // напечатает: Мурка: мяу

Поля (атрибуты) хранят состояние объекта, методы задают его поведение. В примере name — поле, meow() — метод, а murka — объект класса Cat.

Четыре принципа ООП

Четыре столпа отвечают на разные вопросы проектирования. Сводная таблица — суть каждого принципа и что он дает на практике.

Принцип Суть Что дает
Инкапсуляция Данные и работающие с ними методы собраны в одном объекте, доступ к внутренностям ограничен Защита состояния от случайной порчи, контроль инвариантов
Наследование Новый класс берет поля и методы существующего и дополняет их Переиспользование кода, общий тип для родственных классов
Полиморфизм Один вызов метода срабатывает по-разному в зависимости от типа объекта Единый интерфейс для разных реализаций, расширяемость
Абстракция В модели остаются только существенные для задачи свойства, детали скрыты Простота использования, работа через понятия предметной области

Инкапсуляция

Инкапсуляция — это объединение данных и методов в одном объекте вместе с ограничением прямого доступа к внутренним данным. Снаружи объект дергают через публичные методы, а поля прячут (в Java — модификатором private). Так объект сам следит за корректностью своего состояния.

class Account {
    private long balance;         // поле скрыто, снаружи не изменить напрямую

    public void deposit(long sum) {
        if (sum > 0) {            // инвариант: пополнение только положительное
            balance += sum;
        }
    }

    public long getBalance() {
        return balance;
    }
}

Здесь нельзя присвоить balance отрицательное значение мимо проверки: поле закрыто, а метод deposit пропускает только корректные суммы. Без инкапсуляции любой участок кода мог бы записать в баланс что угодно, и найти источник ошибки было бы тяжело.

Наследование

Наследование позволяет создать класс на основе существующего: потомок получает поля и методы родителя, а затем добавляет свои или переопределяет унаследованные. В Java для этого служит ключевое слово extends. Отношение читается как «является»: автобус является транспортным средством.

class Vehicle {
    protected int speed;
    void move() {
        System.out.println("Двигаюсь со скоростью " + speed);
    }
}

class Bus extends Vehicle {       // Bus наследует поле speed и метод move
    int seats = 45;               // добавили новое поле
}

Объект Bus умеет вызывать move(), хотя этот метод описан в Vehicle. Наследование стоит применять именно для отношения «является», а не «содержит»: если у автобуса есть двигатель, двигатель лучше сделать отдельным полем-объектом, а не родителем.

Полиморфизм

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

class Shape {
    double area() { return 0; }
}

class Circle extends Shape {
    double r = 2;
    @Override double area() { return Math.PI * r * r; }
}

class Square extends Shape {
    double side = 3;
    @Override double area() { return side * side; }
}

Теперь по ссылке типа Shape можно работать с любой фигурой, не зная ее конкретный класс:

Shape s = new Circle();
System.out.println(s.area());     // 12.566370614359172

s = new Square();
System.out.println(s.area());     // 9.0

Вызов один и тот же — s.area(), а результат зависит от того, что реально лежит в s. Это упрощает расширение: чтобы добавить треугольник, достаточно нового класса-наследника, старый код трогать не нужно.

Абстракция

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

abstract class Payment {
    abstract void pay(long amount);   // что делаем, без реализации «как»
}

class CardPayment extends Payment {
    @Override void pay(long amount) {
        System.out.println("Оплата картой: " + amount);
    }
}

Код, который принимает платежи, работает с типом Payment и вызывает pay(), не вникая, картой это, наличными или переводом. Абстракция задает контракт, а конкретные классы его выполняют.

Инкапсуляция и абстракция: не путать

Эти два принципа связаны, но отвечают на разные вопросы.

  • Инкапсуляция — про то, как объект защищает свои данные: поля закрыты, снаружи доступен только контролируемый набор методов. Это механизм сокрытия состояния.
  • Абстракция — про то, что показать наружу как модель: какие понятия и операции важны для задачи. Это выбор уровня детализации.

Коротко: инкапсуляция скрывает внутренние данные, абстракция скрывает сложность реализации за простым понятием. В классе Account закрытое поле balance — инкапсуляция; сама идея «счет умеет пополняться и показывать баланс» без подробностей хранения — абстракция.

Наследование и полиморфизм: не одно и то же

Наследование и полиморфизм тоже часто смешивают, потому что в примерах они идут вместе.

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

Наследование — один из механизмов, который включает полиморфизм, но не единственный: в Java полиморфизм работает и через интерфейсы без наследования класса. Наследование отвечает на вопрос «откуда берутся члены класса», полиморфизм — на вопрос «чей метод вызовется».

Зачем нужен ООП

ООП удобно, когда программу естественно описать как набор взаимодействующих сущностей: пользователи, заказы, платежи, фигуры на экране. Основные выгоды подхода:

  • Модульность: логика и данные сущности лежат в одном месте, изменения локальны.
  • Переиспользование: общее поведение выносится в родителя или интерфейс, не дублируется.
  • Расширяемость: новый вариант добавляется новым классом, старый код часто остается нетронутым.

ООП — не универсальная замена другим подходам. Для задач, где в центре вычисления и преобразования данных, а не сущности с поведением, функциональный или процедурный стиль бывает проще и короче. Многие языки (Java, C#, Python) поддерживают несколько парадигм, и их комбинируют.

Выводы

  • ООП строит программу из объектов, каждый из которых хранит данные и поведение; класс — это шаблон, объект — его конкретный экземпляр.
  • Четыре принципа: инкапсуляция (защита данных), наследование (переиспользование через иерархию), полиморфизм (единый вызов, разное поведение), абстракция (модель без деталей реализации).
  • Инкапсуляция скрывает внутренние данные, абстракция скрывает сложность за простым понятием — это разные принципы.
  • Наследование дает члены родителя, полиморфизм выбирает реализацию по типу объекта; наследование — лишь один из путей к полиморфизму.
  • ООП силен для предметных областей с сущностями и поведением, но не обязателен там, где в центре вычисления.

Где применяется / связь с практикой

Принципы ООП — это база для промышленной разработки на Java, C#, Kotlin, Python и большинстве современных языков. На них держатся типовые архитектурные приемы (слои, сервисы, репозитории) и почти все популярные фреймворки, поэтому без понимания четырех столпов трудно читать чужой код и проектировать свой.

Освойте тему на практике

Разобрать инкапсуляцию, наследование, полиморфизм и абстракцию на живых задачах с проверкой наставника удобно на курсе Java Basic. Если хотите сперва посмотреть формат и уровень, загляните на открытые уроки Otus — там разбирают темы вживую и отвечают на вопросы.

FAQ

Обязательно ли использовать все четыре принципа сразу?
Нет, это не чек-лист. Инкапсуляция применяется почти всегда, а наследование и полиморфизм — по необходимости; навязывать иерархию там, где ее нет, вредно.

Чем интерфейс отличается от абстрактного класса в Java?
Абстрактный класс может хранить поля и готовые методы и наследуется только один, а интерфейс задает набор методов-обязательств, и класс может реализовать сразу несколько интерфейсов.

Можно ли писать на ООП-языке без объектов?
Да, короткий скрипт может обойтись статическими методами и процедурным стилем. ООП раскрывается на задачах, где есть сущности со своим состоянием и поведением.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)