Каковы четыре основных принципа объектно-ориентированного программирования (ООП)?

«Каковы четыре основных принципа объектно-ориентированного программирования (ООП)?» — вопрос из категории Основы программирования, который задают на 10% собеседований QA Тестировщик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Объектно-ориентированное программирование базируется на четырех основных принципах (столпах), которые определяют его архитектуру и подход к проектированию.

1. Инкапсуляция (Encapsulation)

Объединение данных (полей) и методов (функций), которые работают с этими данными, в единый объект, и сокрытие внутреннего состояния объекта от прямого доступа извне.

Цель: Управление доступом, предотвращение недопустимых изменений данных, упрощение интерфейса объекта.

Реализация: Использование модификаторов доступа (private, protected, public).

Пример на Java:

public class BankAccount {
    // Данные (поля) скрыты (инкапсулированы)
    private String owner;
    private double balance;

    // Публичный интерфейс для работы с объектом
    public BankAccount(String owner, double initialBalance) {
        this.owner = owner;
        this.balance = initialBalance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            return true;
        }
        return false;
    }

    // Геттер предоставляет доступ к данным только для чтения
    public double getBalance() {
        return balance;
    }
}
// Использование:
BankAccount account = new BankAccount("Alice", 1000);
account.deposit(500);
// account.balance = -1000; // ОШИБКА КОМПИЛЯЦИИ: поле private
System.out.println(account.getBalance()); // Корректный доступ

2. Наследование (Inheritance)

Механизм, позволяющий создавать новый класс (дочерний, производный) на основе существующего класса (родительского, базового), заимствуя его свойства и поведение.

Цель: Повторное использование кода, создание иерархии классов, реализация отношения «является» (is-a).

Пример на Python:

class Animal:  # Базовый класс
    def __init__(self, name):
        self.name = name
    def make_sound(self):
        pass  # Общий интерфейс

class Dog(Animal):  # Дочерний класс
    def make_sound(self):  # Переопределение метода
        return "Woof!"
    def fetch(self):       # Собственный метод
        return f"{self.name} fetches the ball."

class Cat(Animal):
    def make_sound(self):
        return "Meow!"

animals = [Dog("Rex"), Cat("Whiskers")]
for a in animals:
    print(f"{a.name}: {a.make_sound()}")
# Вывод:
# Rex: Woof!
# Whiskers: Meow!

3. Полиморфизм (Polymorphism)

Способность объектов с одинаковой спецификацией (интерфейсом) иметь различную реализацию.

Цель: Единый интерфейс для операций над разными типами данных, что повышает гибкость и расширяемость кода.

Виды:

  • Полиморфизм во время выполнения (динамический): Переопределение методов (как в примере с Animal выше).
  • Полиморфизм во время компиляции (статический): Перегрузка методов (method overloading).

Пример (динамический полиморфизм в Java):

interface Shape {
    double calculateArea(); // Общий интерфейс
}

class Circle implements Shape {
    private double radius;
    public Circle(double r) { this.radius = r; }
    @Override
    public double calculateArea() { // Своя реализация
        return Math.PI * radius * radius;
    }
}

class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double w, double h) { this.width = w; this.height = h; }
    @Override
    public double calculateArea() { // Своя реализация
        return width * height;
    }
}

// Полиморфное поведение:
List<Shape> shapes = Arrays.asList(new Circle(5), new Rectangle(4, 6));
for (Shape shape : shapes) {
    // Вызывается своя реализация calculateArea() для каждого объекта
    System.out.println("Area: " + shape.calculateArea());
}

4. Абстракция (Abstraction)

Процесс выделения существенных характеристик объекта и игнорирования несущественных деталей.

Цель: Сокрытие сложности, работа на уровне концепций, а не реализации.

Реализация: С помощью абстрактных классов (которые могут содержать как реализованные, так и абстрактные методы) и интерфейсов (которые определяют только контракт).

Пример: Класс Car может иметь абстрактный метод startEngine(). Пользователю класса важно знать, что машину можно завести, но не важно, как именно это происходит в двигателях ElectricEngine или CombustionEngine.

Взаимосвязь с SOLID

Принципы SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) являются развитием и конкретизацией основных принципов ООП, направленными на создание более качественной и поддерживаемой архитектуры.