Объектно-ориентированное программирование — это подход к разработке, в котором программа строится из взаимодействующих объектов. Каждый объект объединяет данные (состояние) и процедуры (поведение) так, чтобы их можно было воспринимать как элемент реального мира: кнопку интерфейса, банковский счёт, файл, фигуру на плоскости.
Модель мира: мы описываем сущности и их связи через классы и объекты.
Управление сложностью: скрываем внутренности (инкапсулируем), даём чёткие интерфейсы.
Повторное использование: наследование и композиция помогают переиспользовать код без копирования.
Гибкость: за счёт полиморфизма и интерфейсов код легче расширять.
Базовые понятия
Класс — это шаблон (чертёж) будущих объектов. Объект (экземпляр) — это конкретный представитель класса с конкретным состоянием. Поле хранит данные; метод описывает поведение; конструктор подготавливает объект к работе.
Класс определяет форму и поведение.
Экземпляр — это созданный объект по этому шаблону.
Сигнатура метода — имя + параметры (и их типы).
Интерфейс — контракт: «что умеет» без деталей «как».
Четыре столпа ООП
Инкапсуляция
Скрытие внутренностей и управление правилами работы с состоянием через методы. Это защищает инварианты класса и упрощает сопровождение.
Абстракция
Выделение существенного и «срезание» деталей. Через интерфейсы и абстрактные классы мы программируем «на уровне идей», а не реализаций.
Наследование
Механизм, позволяющий одному классу «унаследовать» состояние и поведение другого. Важно отличать наследование интерфейса (обещание методов) и наследование реализации (перенятие кода).
Полиморфизм
Один интерфейс — много реализаций. Код работает с интерфейсом, а конкретный объект подставляется в рантайме (динамическая диспетчеризация).
Композиция против наследования и агрегации
Наследование описывает отношение «A — это B» (is-a). Композиция — отношение «A состоит из B» (has-a). Композиция менее хрупкая и чаще предпочтительна для повторного использования поведения.
Агрегация — это разновидность композиции, но с более «слабой» связью. В композиции части не могут существовать без целого (двигатель обычно не существует без автомобиля), а в агрегации объект может жить отдельно от «владельца». Например, университет содержит студентов, но студенты могут существовать и без конкретного университета.
«Круг — это Фигура» — разумное наследование.
«Автомобиль состоит из Двигателя» — это композиция, а не наследование.
«Университет содержит Студентов» — это агрегация: студенты могут учиться в разных местах или временно не принадлежать университету.
Примеры: Наследование, Композиция, Агрегация
Пример: Shape и Circle (наследование), Car и Engine (композиция), University и Student (агрегация).
// Наследование
struct Shape { virtual double area() const = 0; };
struct Circle : Shape {
double r;
Circle(double r) : r(r) {}
double area() const override { return 3.14 * r * r; }
};
// Композиция
struct Engine {
int rpm = 0;
void rev(int delta) { rpm += delta; }
};
struct Car {
Engine engine; // часть объекта
void accelerate() { engine.rev(1000); }
};
// Агрегация
struct Student { std::string name; };
struct University {
std::vector<Student*> students; // могут жить отдельно
void add(Student* s) { students.push_back(s); }
};
// Наследование
abstract class Shape { abstract double area(); }
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
double area() { return Math.PI * r * r; }
}
// Композиция
class Engine {
private int rpm = 0;
void rev(int delta) { rpm += delta; }
int getRpm() { return rpm; }
}
class Car {
private final Engine engine = new Engine();
void accelerate() { engine.rev(1000); }
}
// Агрегация
class Student { String name; }
class University {
private List<Student> students = new ArrayList<>();
void add(Student s) { students.add(s); }
}
// Наследование
class Shape {
area() {
throw "abstract";
}
}
class Circle extends Shape {
constructor(r) {
super();
this.r = r;
}
area() {
return Math.PI * this.r * this.r;
}
}
// Композиция
class Engine {
constructor() {
this.rpm = 0;
}
rev(delta) {
this.rpm += delta;
}
}
class Car {
constructor() {
this.engine = new Engine();
}
accelerate() {
this.engine.rev(1000);
}
}
// Агрегация
class Student {
constructor(name) {
this.name = name;
}
}
class University {
constructor() {
this.students = [];
}
add(student) {
this.students.push(student);
}
}
// Наследование
abstract class Shape {
abstract area(): number;
}
class Circle extends Shape {
constructor(private r: number) {
super();
}
area(): number {
return Math.PI * this.r * this.r;
}
}
// Композиция
class Engine {
private rpm = 0;
rev(delta: number) {
this.rpm += delta;
}
}
class Car {
private engine = new Engine();
accelerate() {
this.engine.rev(1000);
}
}
// Агрегация
class Student {
constructor(public name: string) {
}
}
class University {
private students: Student[] = [];
add(s: Student) {
this.students.push(s);
}
}
package main
import "fmt"
// Наследование нет, используем интерфейсы
type Shape interface { Area() float64 }
type Circle struct { R float64 }
func (c Circle) Area() float64 { return 3.14 * c.R * c.R }
// Композиция
type Engine struct { Rpm int }
func (e *Engine) Rev(delta int) { e.Rpm += delta }
type Car struct { Engine } // встраивание (композиция)
func (c *Car) Accelerate() { c.Rev(1000) }
// Агрегация
type Student struct { Name string }
type University struct { Students []*Student }
func (u *University) Add(s *Student) { u.Students = append(u.Students, s) }
func main() {
s := &Student{"Bob"}
u := &University{}
u.Add(s)
fmt.Println(u.Students[0].Name) // Bob
}
' Наследование в VBA нет, только интерфейсы через Implements
' Композиция
' Class Module: Engine
Public Rpm As Integer
Public Sub Rev(delta As Integer)
Rpm = Rpm + delta
End Sub
' Class Module: Car
Private eng As Engine
Private Sub Class_Initialize()
Set eng = New Engine
End Sub
Public Sub Accelerate()
eng.Rev 1000
End Sub
' Агрегация
' Class Module: Student
Public Name As String
' Class Module: University
Private students As Collection
Private Sub Class_Initialize()
Set students = New Collection
End Sub
Public Sub Add(s As Student)
students.Add s
End Sub
Интерфейсы, абстрактные классы, контракты
Интерфейсы задают контракт поведения. Абстрактные классы могут содержать общую реализацию. Контракт включает инварианты, предусловия и постусловия.
interface Shape { double area(); }
class Rect implements Shape {
double w, h;
Rect(double w, double h) { this.w = w; this.h = h; }
public double area() { return w \* h; }
}
public class Main {
public static void main(String\[] args) {
Shape s = new Rect(3, 4);
System.out.println(s.area()); // 12
}
}
class Rect {
constructor(w, h) {
this.w = w;
this.h = h;
}
area() {
return this.w * this.h;
}
}
const s = new Rect(3, 4);
console.log(s.area()); // 12
interface Shape {
area(): number;
}
class Rect implements Shape {
constructor(public w: number, public h: number) {
}
area(): number {
return this.w * this.h;
}
}
const s: Shape = new Rect(3, 4);
console.log(s.area()); // 12
package main
import (
"fmt"
)
type Shape interface { Area() float64 }
type Rect struct { W, H float64 }
func (r Rect) Area() float64 { return r.W \* r.H }
func main() {
var s Shape = Rect{3, 4}
fmt.Println(s.Area()) // 12
}
' Class Module: Rect
' Public W As Double
' Public H As Double
' Public Function Area() As Double
' Area = W \* H
' End Function
' Module
Sub Demo()
Dim s As Rect
Set s = New Rect
s.W = 3
s.H = 4
Debug.Print s.Area ' 12
End Sub
Практические рекомендации
Названия: классы — существительные, методы — глаголы. Из названия понятна ответственность.
Программируйте против абстракций: зависимости — через интерфейсы, передавайте их снаружи.
Не злоупотребляйте наследованием: если сомневаетесь — берите композицию.
Инварианты: защищайте корректность состояния внутри класса (проверки в методах, не только при создании).
Частые подводные камни
Хрупкий базовый класс: изменения в родителе ломают наследников. Смягчайте через композицию и тесты контрактов.
Алмаз наследования (C++): множественное наследование порождает неоднозначность. Решение — виртуальное наследование или отказ от него.
Утечки инкапсуляции: возврат «сырого» изменяемого состояния наружу (getItems() отдаёт изменяемый список). Возвращайте копии или иммутабельные представления.
Сложные конструкторы: тяжёлые операции при создании усложняют ошибки/тесты. Используйте фабрики/ленивую инициализацию.