Программа курса← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

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

Введение

О сокрытии полей мы говорили уже не раз: и когда разбирали, почему пробег меняет только метод drive, и когда смотрели, зачем нужны свойства. Каждый раз это звучало вскользь – с пометкой «подробнее позже». Здесь и разберёмся: зачем закрывать внутренности объекта на самом деле и какими механизмами это делают разные языки.

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

Проще говоря, инкапсуляция – это:

  • Сокрытие реализации – доступно только то, что нужно
  • Защита данных от некорректного использования
  • Контроль доступа к внутреннему состоянию объекта

Чем языки закрывают внутренности

Инкапсуляция – это решение проектировщика, а не возможность языка. Язык лишь даёт инструмент, которым это решение записывают, и инструменты у разных языков устроены принципиально по-разному. Подходов ровно три.

Подход первый: модификаторы доступа

Самый распространённый вариант: у каждого члена класса есть пометка, кто имеет к нему доступ, и компилятор следит за её соблюдением. Так устроены C#, C++, Java и Kotlin – но даже у них наборы не совпадают.

Уровень C# C++ Java Kotlin
Только внутри класса private private private private
Класс и наследники protected protected protected protected
Своя сборка или модуль internal internal
Свой пакет по умолчанию
Всем public public public public (по умолчанию)

Несколько неочевидных различий, о которые спотыкаются при переходе между языками:

  • Умолчание везде разное. В C# член класса без модификатора – private, в Kotlin – public, в Java – виден всему пакету, в C++ зависит от того, class объявлен или struct.
  • «Свой модуль» понимается по-разному. internal в C# – это сборка, internal в Kotlin – модуль сборки, а в Java аналог – пакет, и он не имеет ключевого слова вовсе.
  • В C++ есть friend – возможность выдать доступ к закрытым членам конкретному классу или функции. Ни в одном из остальных языков такой точечной выдачи нет.
  • protected в Java шире, чем кажется: он открывает доступ не только наследникам, но и всему пакету.

Подход второй: видимость по имени

Go обходится без модификаторов вообще. Видно имя или нет, определяет регистр первой буквы: с заглавной – доступно из других пакетов, со строчной – нет.

type Account struct {
	Owner   string  // видно снаружи пакета
	balance float64 // видно только внутри пакета
}

Разница с первым подходом не косметическая. Единица сокрытия здесь не класс, а пакет: любой код внутри пакета видит balance у любого Account. Отдельного «закрыто даже от соседнего типа» в Go просто нет – и это осознанный выбор авторов языка, а не упущение.

Подход третий: договорённость

В Python механизма защиты нет вовсе. Есть соглашение: имя с одним подчёркиванием – «это внутреннее, снаружи не трогай».

class Account:
    def __init__(self):
        self._balance = 0    # договорённость: не трогать
        self.__secret = 0    # искажение имени, но всё ещё не защита

Два подчёркивания включают искажение имени (name mangling): атрибут становится доступен как _Account__secret. Это защита от случайного совпадения имён в иерархии, а не от доступа – добраться до значения по-прежнему можно одной строкой.

Язык здесь сознательно доверяет программисту. Известная формулировка сообщества: «мы все здесь взрослые люди».

Отдельный случай: TypeScript

TypeScript интересен тем, что содержит сразу два механизма из разных подходов.

class Account {
    private balance = 0;  // проверяет компилятор, в рантайме поля обычные
    #secret = 0;          // закрыто самим движком JavaScript
}

private существует только во время компиляции: после сборки в JavaScript пометка исчезает, и поле доступно как обычное. А # – настоящее приватное поле, обращение к нему извне класса не скомпилируется и не сработает в рантайме.

Что из этого следует

Механизмы разные, но ни один из них не защищает от намеренного обхода. Рефлексия в C# и Java достаёт закрытые поля, в C++ помогают приведения указателей, в Python достаточно знать правило искажения имён.

Отсюда правильный способ думать об инкапсуляции: пометка доступа адресована не злоумышленнику, а коллеге и вам самому через полгода. Она говорит «на это не рассчитывай, оно может измениться» – и в этом смысле она одинаково работает и в C# с его пятью модификаторами, и в Python, где нет ни одного.

Практические примеры инкапсуляции в C#

❌ Плохой подход (без инкапсуляции)

public class BankAccount
{
    public decimal balance; // Публичное поле - опасно!
    
    public void Deposit(decimal amount)
    {
        balance += amount;
    }
}

// Использование (проблемы):
BankAccount account = new BankAccount();
account.balance = 1000; // Может напрямую изменить баланс
account.balance = -500; // Может установить отрицательное значение - логическая ошибка!
class BankAccount {
public:
    double balance = 0; // Публичное поле – опасно!

    void Deposit(double amount) { balance += amount; }
};

// Использование (проблемы):
BankAccount account;
account.balance = 1000; // Может напрямую изменить баланс
account.balance = -500; // Может установить отрицательное значение – логическая ошибка!
type BankAccount struct {
	Balance float64 // Экспортированное поле – опасно!
}

func (a *BankAccount) Deposit(amount float64) {
	a.Balance += amount
}

// Использование (проблемы):
account := &BankAccount{}
account.Balance = 1000 // Может напрямую изменить баланс
account.Balance = -500 // Может установить отрицательное значение – логическая ошибка!
class BankAccount:
    def __init__(self):
        self.balance = 0  # Публичный атрибут – опасно!

    def deposit(self, amount):
        self.balance += amount

# Использование (проблемы):
account = BankAccount()
account.balance = 1000  # Может напрямую изменить баланс
account.balance = -500  # Может установить отрицательное значение – логическая ошибка!
class BankAccount {
    balance = 0; // Публичное поле – опасно!

    deposit(amount: number): void {
        this.balance += amount;
    }
}

// Использование (проблемы):
const account = new BankAccount();
account.balance = 1000; // Может напрямую изменить баланс
account.balance = -500; // Может установить отрицательное значение – логическая ошибка!
public class BankAccount {
    public BigDecimal balance = BigDecimal.ZERO; // Публичное поле – опасно!

    public void deposit(BigDecimal amount) {
        balance = balance.add(amount);
    }
}

// Использование (проблемы):
BankAccount account = new BankAccount();
account.balance = new BigDecimal("1000");  // Может напрямую изменить баланс
account.balance = new BigDecimal("-500");  // Отрицательное значение – логическая ошибка!
class BankAccount {
    var balance: Double = 0.0 // Публичное свойство – опасно!

    fun deposit(amount: Double) {
        balance += amount
    }
}

// Использование (проблемы):
val account = BankAccount()
account.balance = 1000.0 // Может напрямую изменить баланс
account.balance = -500.0 // Может установить отрицательное значение – логическая ошибка!

✅ Хороший подход (с инкапсуляцией)

Пример 1: базовый пример с банковским счётом

public class BankAccount
{
    private decimal balance; // Приватное поле - защищено от прямого доступа
    
    public void Deposit(decimal amount)
    {
        if (amount > 0)
            balance += amount;
        else
            throw new ArgumentException("Сумма должна быть положительной");
    }
    
    public void Withdraw(decimal amount)
    {
        if (amount > 0 && amount <= balance)
            balance -= amount;
        else
            throw new InvalidOperationException("Недостаточно средств или неверная сумма");
    }
    
    // Метод для получения баланса (только чтение)
    public decimal GetBalance()
    {
        return balance;
    }
}

// Использование:
BankAccount account = new BankAccount();
account.Deposit(1000);
account.Withdraw(500);
// account.balance = 1000; // Ошибка компиляции - поле приватное!
Console.WriteLine(account.GetBalance()); // 500
#include <stdexcept>

class BankAccount {
public:
    void Deposit(double amount) {
        if (amount <= 0) {
            throw std::invalid_argument("Сумма должна быть положительной");
        }

        balance_ += amount;
    }

    void Withdraw(double amount) {
        if (amount <= 0 || amount > balance_) {
            throw std::logic_error("Недостаточно средств или неверная сумма");
        }

        balance_ -= amount;
    }

    // Метод для получения баланса (только чтение)
    double GetBalance() const { return balance_; }

private:
    double balance_ = 0; // Приватное поле – защищено от прямого доступа
};
type BankAccount struct {
	balance float64 // Неэкспортированное поле – снаружи пакета недоступно
}

func (a *BankAccount) Deposit(amount float64) error {
	if amount <= 0 {
		return errors.New("сумма должна быть положительной")
	}

	a.balance += amount

	return nil
}

func (a *BankAccount) Withdraw(amount float64) error {
	if amount <= 0 || amount > a.balance {
		return errors.New("недостаточно средств или неверная сумма")
	}

	a.balance -= amount

	return nil
}

// Метод для получения баланса (только чтение)
func (a *BankAccount) Balance() float64 {
	return a.balance
}
class BankAccount:
    def __init__(self):
        self.__balance = 0  # Два подчёркивания включают искажение имени

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Сумма должна быть положительной")

        self.__balance += amount

    def withdraw(self, amount):
        if amount <= 0 or amount > self.__balance:
            raise ValueError("Недостаточно средств или неверная сумма")

        self.__balance -= amount

    # Метод для получения баланса (только чтение)
    def get_balance(self):
        return self.__balance

# Использование:
account = BankAccount()
account.deposit(1000)
account.withdraw(500)
# account.__balance = 1000  # Не изменит внутреннее поле
print(account.get_balance())  # 500
class BankAccount {
    #balance = 0; // Приватное поле – защищено на уровне языка

    deposit(amount: number): void {
        if (amount <= 0) {
            throw new Error("Сумма должна быть положительной");
        }

        this.#balance += amount;
    }

    withdraw(amount: number): void {
        if (amount <= 0 || amount > this.#balance) {
            throw new Error("Недостаточно средств или неверная сумма");
        }

        this.#balance -= amount;
    }

    // Метод для получения баланса (только чтение)
    getBalance(): number {
        return this.#balance;
    }
}

const account = new BankAccount();
account.deposit(1000);
account.withdraw(500);
console.log(account.getBalance()); // 500
public class BankAccount {
    private BigDecimal balance = BigDecimal.ZERO; // Приватное поле – защищено

    public void deposit(BigDecimal amount) {
        if (amount.signum() <= 0) {
            throw new IllegalArgumentException("Сумма должна быть положительной");
        }

        balance = balance.add(amount);
    }

    public void withdraw(BigDecimal amount) {
        if (amount.signum() <= 0 || amount.compareTo(balance) > 0) {
            throw new IllegalStateException("Недостаточно средств или неверная сумма");
        }

        balance = balance.subtract(amount);
    }

    // Метод для получения баланса (только чтение)
    public BigDecimal getBalance() {
        return balance;
    }
}
class BankAccount {
    // Свойство с закрытым сеттером: читать можно, менять – только внутри класса
    var balance: Double = 0.0
        private set

    fun deposit(amount: Double) {
        require(amount > 0) { "Сумма должна быть положительной" }

        balance += amount
    }

    fun withdraw(amount: Double) {
        check(amount > 0 && amount <= balance) { "Недостаточно средств или неверная сумма" }

        balance -= amount
    }
}

val account = BankAccount()
account.deposit(1000.0)
account.withdraw(500.0)
// account.balance = 1000.0 // Ошибка компиляции – сеттер закрыт
println(account.balance) // 500.0

Пример 2: Использование свойств (Properties)

Базовые свойства с валидацией:

public class Person
{
    private string name;
    private int age;
    
    // Свойство с валидацией
    public string Name
    {
        get { return name; }
        set 
        { 
            if (!string.IsNullOrWhiteSpace(value))
                name = value;
            else
                throw new ArgumentException("Имя не может быть пустым");
        }
    }
    
    public int Age
    {
        get { return age; }
        set 
        { 
            if (value >= 0 && value <= 150)
                age = value;
            else
                throw new ArgumentException("Некорректный возраст");
        }
    }
}

// Использование:
Person person = new Person();
person.Name = "Иван";     // Вызывается set
person.Age = 25;          // Вызывается set
Console.WriteLine(person.Name); // Вызывается get

Свойств в C++ нет: поле закрывают, а проверку кладут в метод-сеттер вроде set_age. Обращение снаружи выглядит как вызов, а не как присваивание.

Свойств в Go нет: поле пишут с маленькой буквы, а рядом кладут методы Age и SetAge, причём второй возвращает ошибку вместо исключения.

class Person:
    def __init__(self):
        self._name = ""
        self._age = 0

    # Свойство с валидацией
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value or not value.strip():
            raise ValueError("Имя не может быть пустым")

        self._name = value

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if not 0 <= value <= 150:
            raise ValueError("Некорректный возраст")

        self._age = value

# Использование:
person = Person()
person.name = "Иван"     # Вызывается setter
person.age = 25          # Вызывается setter
print(person.name)       # Вызывается getter
class Person {
    #name = "";
    #age = 0;

    // Аксессоры играют роль свойства с валидацией
    get name(): string {
        return this.#name;
    }

    set name(value: string) {
        if (!value.trim()) {
            throw new Error("Имя не может быть пустым");
        }

        this.#name = value;
    }

    get age(): number {
        return this.#age;
    }

    set age(value: number) {
        if (value < 0 || value > 150) {
            throw new Error("Некорректный возраст");
        }

        this.#age = value;
    }
}

const person = new Person();
person.name = "Иван";  // Вызывается set
person.age = 25;       // Вызывается set
console.log(person.name); // Вызывается get

Свойств в Java нет: их роль по соглашению играют getAge и setAge, и вся проверка живёт в сеттере.

class Person {
    private var _name = ""
    private var _age = 0

    // Свойство с валидацией: поле хранится отдельно
    var name: String
        get() = _name
        set(value) {
            require(value.isNotBlank()) { "Имя не может быть пустым" }
            _name = value
        }

    var age: Int
        get() = _age
        set(value) {
            require(value in 0..150) { "Некорректный возраст" }
            _age = value
        }
}

val person = Person()
person.name = "Иван"  // Вызывается set
person.age = 25       // Вызывается set
println(person.name)  // Вызывается get

Автоматические свойства:

public class Product
{
    // Компилятор сам создаст приватное поле
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    // Свойство только для чтения
    public string ProductCode { get; }
    
    public Product(string code)
    {
        ProductCode = code; // Можно установить только в конструкторе
    }
}

Автоматических свойств в C++ нет: поле объявляют явно, а доступ к нему дают методы.

В Go свойств нет вовсе: есть поля и методы, автоматически ничего не создаётся.

class Product:
    def __init__(self, code):
        # Обычные атрибуты – аналог автоматических свойств
        self.name = ""
        self.price = 0
        self.__product_code = code  # Можно установить только в конструкторе

    # Свойство только для чтения
    @property
    def product_code(self):
        return self.__product_code
class Product {
    name = "";
    price = 0;

    // readonly – значение можно задать только в конструкторе
    constructor(public readonly productCode: string) {}
}

В Java свойств нет: геттеры и сеттеры пишут руками или генерируют средой разработки.

class Product(
    // val – только для чтения, задаётся при создании
    val productCode: String,
) {
    // var – обычные изменяемые свойства, поле создаётся автоматически
    var name: String = ""
    var price: Double = 0.0
}

Пример 3: Различные типы свойств

public class Temperature
{
    private double celsius;
    
    // Свойство только для чтения
    public double Celsius 
    { 
        get { return celsius; } 
    }
    
    // Вычисляемое свойство
    public double Fahrenheit
    {
        get { return celsius * 9 / 5 + 32; }
    }
    
    // Свойство с приватным set
    public string MeasurementDate { get; private set; }
    
    public void SetTemperature(double celsius, string date)
    {
        if (celsius >= -273.15) // Абсолютный ноль
        {
            this.celsius = celsius;
            MeasurementDate = date;
        }
        else
            throw new ArgumentException("Температура не может быть ниже абсолютного нуля");
    }
}

// Использование:
Temperature temp = new Temperature();
temp.SetTemperature(25, "2024-01-15");
Console.WriteLine($"{temp.Celsius}°C = {temp.Fahrenheit}°F");
// temp.Celsius = 30; // Ошибка - свойство только для чтения
class Temperature {
public:
    // Только для чтения: сеттера нет
    double celsius() const { return celsius_; }

    // Вычисляемое значение
    double fahrenheit() const { return celsius_ * 9 / 5 + 32; }

    const std::string& measurement_date() const { return measurement_date_; }

    void SetTemperature(double celsius, const std::string& date) {
        if (celsius < -273.15) { // Абсолютный ноль
            throw std::invalid_argument("Температура не может быть ниже абсолютного нуля");
        }

        celsius_ = celsius;
        measurement_date_ = date;
    }

private:
    double celsius_ = 0;
    std::string measurement_date_;
};
type Temperature struct {
	celsius         float64
	measurementDate string
}

// Только для чтения
func (t *Temperature) Celsius() float64 { return t.celsius }

// Вычисляемое значение
func (t *Temperature) Fahrenheit() float64 { return t.celsius*9/5 + 32 }

func (t *Temperature) MeasurementDate() string { return t.measurementDate }

func (t *Temperature) SetTemperature(celsius float64, date string) error {
	if celsius < -273.15 { // Абсолютный ноль
		return errors.New("температура не может быть ниже абсолютного нуля")
	}

	t.celsius = celsius
	t.measurementDate = date

	return nil
}
class Temperature:
    def __init__(self):
        self.__celsius = 0
        self.__measurement_date = ""

    # Свойство только для чтения
    @property
    def celsius(self):
        return self.__celsius

    # Вычисляемое свойство
    @property
    def fahrenheit(self):
        return self.__celsius * 9 / 5 + 32

    @property
    def measurement_date(self):
        return self.__measurement_date

    def set_temperature(self, celsius, date):
        if celsius < -273.15:  # Абсолютный ноль
            raise ValueError("Температура не может быть ниже абсолютного нуля")

        self.__celsius = celsius
        self.__measurement_date = date

# Использование:
temp = Temperature()
temp.set_temperature(25, "2024-01-15")
print(f"{temp.celsius}°C = {temp.fahrenheit}°F")
# temp.celsius = 30  # Ошибка – свойство только для чтения
class Temperature {
    #celsius = 0;
    #measurementDate = "";

    // Только для чтения: сеттера нет
    get celsius(): number {
        return this.#celsius;
    }

    // Вычисляемое свойство
    get fahrenheit(): number {
        return (this.#celsius * 9) / 5 + 32;
    }

    get measurementDate(): string {
        return this.#measurementDate;
    }

    setTemperature(celsius: number, date: string): void {
        if (celsius < -273.15) {
            throw new Error("Температура не может быть ниже абсолютного нуля");
        }

        this.#celsius = celsius;
        this.#measurementDate = date;
    }
}

const temp = new Temperature();
temp.setTemperature(25, "2024-01-15");
console.log(`${temp.celsius}°C = ${temp.fahrenheit}°F`);
// temp.celsius = 30; // Ошибка – сеттера нет
public class Temperature {
    private double celsius;
    private String measurementDate;

    // Только для чтения – сеттера нет
    public double getCelsius() {
        return celsius;
    }

    // Вычисляемое значение
    public double getFahrenheit() {
        return celsius * 9 / 5 + 32;
    }

    public String getMeasurementDate() {
        return measurementDate;
    }

    public void setTemperature(double celsius, String date) {
        if (celsius < -273.15) { // Абсолютный ноль
            throw new IllegalArgumentException("Температура не может быть ниже абсолютного нуля");
        }

        this.celsius = celsius;
        measurementDate = date;
    }
}
class Temperature {
    // Свойство с закрытым сеттером
    var celsius: Double = 0.0
        private set

    var measurementDate: String = ""
        private set

    // Вычисляемое свойство: поля под ним нет
    val fahrenheit: Double get() = celsius * 9 / 5 + 32

    fun setTemperature(celsius: Double, date: String) {
        require(celsius >= -273.15) { "Температура не может быть ниже абсолютного нуля" }

        this.celsius = celsius
        measurementDate = date
    }
}

val temp = Temperature()
temp.setTemperature(25.0, "2024-01-15")
println("${temp.celsius}°C = ${temp.fahrenheit}°F")
// temp.celsius = 30.0 // Ошибка – сеттер закрыт

Пример 4: Полная инкапсуляция с бизнес-логикой

public class Order
{
    private List<OrderItem> items = new List<OrderItem>();
    private decimal discount;
    
    // Инкапсулированная коллекция (только для чтения)
    public IReadOnlyList<OrderItem> Items => items.AsReadOnly();
    
    public decimal Subtotal => items.Sum(item => item.TotalPrice);
    public decimal Discount 
    { 
        get { return discount; }
        private set 
        { 
            if (value >= 0 && value <= 0.5m) // Максимум 50% скидки
                discount = value;
        }
    }
    
    public decimal Total => Subtotal * (1 - Discount);
    
    // Публичные методы для работы с заказом
    public void AddItem(Product product, int quantity)
    {
        if (quantity <= 0)
            throw new ArgumentException("Количество должно быть положительным");
            
        var existingItem = items.FirstOrDefault(i => i.ProductId == product.Id);
        if (existingItem != null)
            existingItem.Quantity += quantity;
        else
            items.Add(new OrderItem(product, quantity));
    }
    
    public void RemoveItem(int productId, int quantity)
    {
        var item = items.FirstOrDefault(i => i.ProductId == productId);
        if (item != null)
        {
            if (item.Quantity <= quantity)
                items.Remove(item);
            else
                item.Quantity -= quantity;
        }
    }
    
    public void ApplyDiscount(decimal discountPercent)
    {
        Discount = discountPercent / 100;
    }
}

public class OrderItem
{
    public int ProductId { get; }
    public string ProductName { get; }
    public decimal UnitPrice { get; }
    public int Quantity { get; set; }
    public decimal TotalPrice => UnitPrice * Quantity;
    
    public OrderItem(Product product, int quantity)
    {
        ProductId = product.Id;
        ProductName = product.Name;
        UnitPrice = product.Price;
        Quantity = quantity;
    }
}
class Order {
public:
    // Инкапсулированная коллекция: наружу отдаём const-ссылку
    const std::vector<OrderItem>& items() const { return items_; }

    double Subtotal() const {
        double total = 0;
        for (const auto& item : items_) {
            total += item.TotalPrice();
        }

        return total;
    }

    double discount() const { return discount_; }

    double Total() const { return Subtotal() * (1 - discount_); }

    void AddItem(const Product& product, int quantity) {
        if (quantity <= 0) {
            throw std::invalid_argument("Количество должно быть положительным");
        }

        for (auto& item : items_) {
            if (item.product_id() == product.id) {
                item.set_quantity(item.quantity() + quantity);
                return;
            }
        }

        items_.emplace_back(product, quantity);
    }

    void ApplyDiscount(double discount_percent) {
        const double value = discount_percent / 100;
        if (value >= 0 && value <= 0.5) { // Максимум 50% скидки
            discount_ = value;
        }
    }

private:
    std::vector<OrderItem> items_;
    double discount_ = 0;
};

class OrderItem {
public:
    OrderItem(const Product& product, int quantity)
        : product_id_(product.id), product_name_(product.name),
          unit_price_(product.price), quantity_(quantity) {}

    int product_id() const { return product_id_; }
    int quantity() const { return quantity_; }
    void set_quantity(int value) { quantity_ = value; }

    double TotalPrice() const { return unit_price_ * quantity_; }

private:
    int product_id_;
    std::string product_name_;
    double unit_price_;
    int quantity_;
};
type Order struct {
	items    []OrderItem
	discount float64
}

// Наружу отдаём копию среза: изменить внутренний список снаружи нельзя
func (o *Order) Items() []OrderItem {
	return append([]OrderItem(nil), o.items...)
}

func (o *Order) Subtotal() float64 {
	total := 0.0
	for _, item := range o.items {
		total += item.TotalPrice()
	}

	return total
}

func (o *Order) Discount() float64 { return o.discount }

func (o *Order) Total() float64 { return o.Subtotal() * (1 - o.discount) }

func (o *Order) AddItem(product Product, quantity int) error {
	if quantity <= 0 {
		return errors.New("количество должно быть положительным")
	}

	for i := range o.items {
		if o.items[i].ProductID == product.ID {
			o.items[i].Quantity += quantity
			return nil
		}
	}

	o.items = append(o.items, NewOrderItem(product, quantity))

	return nil
}

func (o *Order) ApplyDiscount(percent float64) {
	value := percent / 100
	if value >= 0 && value <= 0.5 { // Максимум 50% скидки
		o.discount = value
	}
}

type OrderItem struct {
	ProductID   int
	ProductName string
	UnitPrice   float64
	Quantity    int
}

func NewOrderItem(product Product, quantity int) OrderItem {
	return OrderItem{
		ProductID:   product.ID,
		ProductName: product.Name,
		UnitPrice:   product.Price,
		Quantity:    quantity,
	}
}

func (i OrderItem) TotalPrice() float64 { return i.UnitPrice * float64(i.Quantity) }
class Order:
    def __init__(self):
        self.__items: list[OrderItem] = []
        self.__discount = 0

    # Инкапсулированная коллекция: наружу отдаём кортеж, а не сам список
    @property
    def items(self):
        return tuple(self.__items)

    @property
    def subtotal(self):
        return sum(item.total_price for item in self.__items)

    @property
    def discount(self):
        return self.__discount

    @property
    def total(self):
        return self.subtotal * (1 - self.__discount)

    def add_item(self, product, quantity):
        if quantity <= 0:
            raise ValueError("Количество должно быть положительным")

        existing = next((i for i in self.__items if i.product_id == product.id), None)
        if existing:
            existing.quantity += quantity
        else:
            self.__items.append(OrderItem(product, quantity))

    def remove_item(self, product_id, quantity):
        item = next((i for i in self.__items if i.product_id == product_id), None)
        if item is None:
            return

        if item.quantity <= quantity:
            self.__items.remove(item)
        else:
            item.quantity -= quantity

    def apply_discount(self, discount_percent):
        value = discount_percent / 100
        if 0 <= value <= 0.5:  # Максимум 50% скидки
            self.__discount = value

class OrderItem:
    def __init__(self, product, quantity):
        self.product_id = product.id
        self.product_name = product.name
        self.unit_price = product.price
        self.quantity = quantity

    @property
    def total_price(self):
        return self.unit_price * self.quantity
class Order {
    #items: OrderItem[] = [];
    #discount = 0;

    // Инкапсулированная коллекция (только для чтения)
    get items(): readonly OrderItem[] {
        return this.#items;
    }

    get subtotal(): number {
        return this.#items.reduce((sum, item) => sum + item.totalPrice, 0);
    }

    get discount(): number {
        return this.#discount;
    }

    get total(): number {
        return this.subtotal * (1 - this.#discount);
    }

    addItem(product: Product, quantity: number): void {
        if (quantity <= 0) {
            throw new Error("Количество должно быть положительным");
        }

        const existing = this.#items.find((i) => i.productId === product.id);
        if (existing) {
            existing.quantity += quantity;
        } else {
            this.#items.push(new OrderItem(product, quantity));
        }
    }

    removeItem(productId: number, quantity: number): void {
        const index = this.#items.findIndex((i) => i.productId === productId);
        if (index < 0) return;

        const item = this.#items[index];
        if (item.quantity <= quantity) {
            this.#items.splice(index, 1);
        } else {
            item.quantity -= quantity;
        }
    }

    applyDiscount(discountPercent: number): void {
        const value = discountPercent / 100;
        if (value >= 0 && value <= 0.5) {
            this.#discount = value;
        }
    }
}

class OrderItem {
    readonly productId: number;
    readonly productName: string;
    readonly unitPrice: number;

    constructor(product: Product, public quantity: number) {
        this.productId = product.id;
        this.productName = product.name;
        this.unitPrice = product.price;
    }

    get totalPrice(): number {
        return this.unitPrice * this.quantity;
    }
}
public class Order {
    private final List<OrderItem> items = new ArrayList<>();
    private BigDecimal discount = BigDecimal.ZERO;

    // Инкапсулированная коллекция (только для чтения)
    public List<OrderItem> getItems() {
        return Collections.unmodifiableList(items);
    }

    public BigDecimal getSubtotal() {
        return items.stream()
                .map(OrderItem::getTotalPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public BigDecimal getDiscount() {
        return discount;
    }

    public BigDecimal getTotal() {
        return getSubtotal().multiply(BigDecimal.ONE.subtract(discount));
    }

    public void addItem(Product product, int quantity) {
        if (quantity <= 0) {
            throw new IllegalArgumentException("Количество должно быть положительным");
        }

        items.stream()
                .filter(i -> i.getProductId() == product.getId())
                .findFirst()
                .ifPresentOrElse(
                        i -> i.setQuantity(i.getQuantity() + quantity),
                        () -> items.add(new OrderItem(product, quantity)));
    }

    public void applyDiscount(BigDecimal discountPercent) {
        BigDecimal value = discountPercent.divide(new BigDecimal("100"));
        if (value.signum() >= 0 && value.compareTo(new BigDecimal("0.5")) <= 0) {
            discount = value;
        }
    }
}

public class OrderItem {
    private final int productId;
    private final String productName;
    private final BigDecimal unitPrice;
    private int quantity;

    public OrderItem(Product product, int quantity) {
        productId = product.getId();
        productName = product.getName();
        unitPrice = product.getPrice();
        this.quantity = quantity;
    }

    public int getProductId() { return productId; }

    public int getQuantity() { return quantity; }

    public void setQuantity(int quantity) { this.quantity = quantity; }

    public BigDecimal getTotalPrice() {
        return unitPrice.multiply(BigDecimal.valueOf(quantity));
    }
}
class Order {
    private val _items = mutableListOf<OrderItem>()

    // Инкапсулированная коллекция: наружу отдаём неизменяемый список
    val items: List<OrderItem> get() = _items

    var discount: Double = 0.0
        private set

    val subtotal: Double get() = _items.sumOf { it.totalPrice }

    val total: Double get() = subtotal * (1 - discount)

    fun addItem(product: Product, quantity: Int) {
        require(quantity > 0) { "Количество должно быть положительным" }

        val existing = _items.find { it.productId == product.id }
        if (existing != null) {
            existing.quantity += quantity
        } else {
            _items += OrderItem(product, quantity)
        }
    }

    fun removeItem(productId: Int, quantity: Int) {
        val item = _items.find { it.productId == productId } ?: return

        if (item.quantity <= quantity) _items -= item else item.quantity -= quantity
    }

    fun applyDiscount(discountPercent: Double) {
        val value = discountPercent / 100
        if (value in 0.0..0.5) discount = value // Максимум 50% скидки
    }
}

class OrderItem(product: Product, var quantity: Int) {
    val productId = product.id
    val productName = product.name
    val unitPrice = product.price

    val totalPrice: Double get() = unitPrice * quantity
}

Пример 5: Инкапсуляция с помощью методов

public class SecuritySystem
{
    private string alarmStatus = "Disarmed";
    private bool isDoorLocked = true;
    private DateTime lastActivity;
    
    // Внутренний приватный метод
    private void UpdateActivity()
    {
        lastActivity = DateTime.Now;
        LogActivity();
    }
    
    private void LogActivity()
    {
        Console.WriteLine($"Activity at {lastActivity}: Alarm {alarmStatus}, Door {(isDoorLocked ? "Locked" : "Unlocked")}");
    }
    
    // Публичный интерфейс
    public void ArmAlarm()
    {
        if (isDoorLocked)
        {
            alarmStatus = "Armed";
            UpdateActivity();
        }
        else
        {
            throw new InvalidOperationException("Нельзя включить сигнализацию при открытой двери");
        }
    }
    
    public void DisarmAlarm(string code)
    {
        if (ValidateCode(code))
        {
            alarmStatus = "Disarmed";
            UpdateActivity();
        }
    }
    
    public void LockDoor()
    {
        isDoorLocked = true;
        UpdateActivity();
    }
    
    public void UnlockDoor(string code)
    {
        if (ValidateCode(code))
        {
            isDoorLocked = false;
            UpdateActivity();
        }
    }
    
    public string GetStatus()
    {
        return $"Alarm: {alarmStatus}, Door: {(isDoorLocked ? "Locked" : "Unlocked")}";
    }
    
    private bool ValidateCode(string code)
    {
        return code == "1234"; // Упрощенная проверка
    }
}
#include <chrono>

class SecuritySystem {
public:
    // Публичный интерфейс
    void ArmAlarm() {
        if (!is_door_locked_) {
            throw std::logic_error("Нельзя включить сигнализацию при открытой двери");
        }

        alarm_status_ = "Armed";
        UpdateActivity();
    }

    void DisarmAlarm(const std::string& code) {
        if (ValidateCode(code)) {
            alarm_status_ = "Disarmed";
            UpdateActivity();
        }
    }

    void LockDoor() {
        is_door_locked_ = true;
        UpdateActivity();
    }

    void UnlockDoor(const std::string& code) {
        if (ValidateCode(code)) {
            is_door_locked_ = false;
            UpdateActivity();
        }
    }

    std::string GetStatus() const {
        return "Alarm: " + alarm_status_ + ", Door: " + DoorState();
    }

private:
    std::string alarm_status_ = "Disarmed";
    bool is_door_locked_ = true;
    std::chrono::system_clock::time_point last_activity_;

    std::string DoorState() const { return is_door_locked_ ? "Locked" : "Unlocked"; }

    // Внутренние приватные методы
    void UpdateActivity() {
        last_activity_ = std::chrono::system_clock::now();
        LogActivity();
    }

    void LogActivity() const {
        std::cout << "Activity: Alarm " << alarm_status_
                  << ", Door " << DoorState() << std::endl;
    }

    bool ValidateCode(const std::string& code) const {
        return code == "1234"; // Упрощённая проверка
    }
};
type SecuritySystem struct {
	alarmStatus  string
	isDoorLocked bool
	lastActivity time.Time
}

func NewSecuritySystem() *SecuritySystem {
	return &SecuritySystem{alarmStatus: "Disarmed", isDoorLocked: true}
}

// Внутренние методы – с маленькой буквы, снаружи пакета не видны
func (s *SecuritySystem) updateActivity() {
	s.lastActivity = time.Now()
	s.logActivity()
}

func (s *SecuritySystem) logActivity() {
	fmt.Printf("Activity at %s: Alarm %s, Door %s\n",
		s.lastActivity.Format(time.RFC3339), s.alarmStatus, s.doorState())
}

func (s *SecuritySystem) doorState() string {
	if s.isDoorLocked {
		return "Locked"
	}

	return "Unlocked"
}

func (s *SecuritySystem) validateCode(code string) bool {
	return code == "1234" // Упрощённая проверка
}

// Публичный интерфейс
func (s *SecuritySystem) ArmAlarm() error {
	if !s.isDoorLocked {
		return errors.New("нельзя включить сигнализацию при открытой двери")
	}

	s.alarmStatus = "Armed"
	s.updateActivity()

	return nil
}

func (s *SecuritySystem) DisarmAlarm(code string) {
	if s.validateCode(code) {
		s.alarmStatus = "Disarmed"
		s.updateActivity()
	}
}

func (s *SecuritySystem) LockDoor() {
	s.isDoorLocked = true
	s.updateActivity()
}

func (s *SecuritySystem) UnlockDoor(code string) {
	if s.validateCode(code) {
		s.isDoorLocked = false
		s.updateActivity()
	}
}

func (s *SecuritySystem) Status() string {
	return fmt.Sprintf("Alarm: %s, Door: %s", s.alarmStatus, s.doorState())
}
from datetime import datetime

class SecuritySystem:
    def __init__(self):
        self.__alarm_status = "Disarmed"
        self.__is_door_locked = True
        self.__last_activity = None

    # Внутренний приватный метод
    def __update_activity(self):
        self.__last_activity = datetime.now()
        self.__log_activity()

    def __log_activity(self):
        door = "Locked" if self.__is_door_locked else "Unlocked"
        print(f"Activity at {self.__last_activity}: Alarm {self.__alarm_status}, Door {door}")

    def __validate_code(self, code):
        return code == "1234"  # Упрощённая проверка

    # Публичный интерфейс
    def arm_alarm(self):
        if not self.__is_door_locked:
            raise RuntimeError("Нельзя включить сигнализацию при открытой двери")

        self.__alarm_status = "Armed"
        self.__update_activity()

    def disarm_alarm(self, code):
        if self.__validate_code(code):
            self.__alarm_status = "Disarmed"
            self.__update_activity()

    def lock_door(self):
        self.__is_door_locked = True
        self.__update_activity()

    def unlock_door(self, code):
        if self.__validate_code(code):
            self.__is_door_locked = False
            self.__update_activity()

    def get_status(self):
        door = "Locked" if self.__is_door_locked else "Unlocked"
        return f"Alarm: {self.__alarm_status}, Door: {door}"
class SecuritySystem {
    #alarmStatus = "Disarmed";
    #isDoorLocked = true;
    #lastActivity: Date | null = null;

    // Внутренние приватные методы
    #updateActivity(): void {
        this.#lastActivity = new Date();
        this.#logActivity();
    }

    #logActivity(): void {
        const door = this.#isDoorLocked ? "Locked" : "Unlocked";
        console.log(`Activity at ${this.#lastActivity}: Alarm ${this.#alarmStatus}, Door ${door}`);
    }

    #validateCode(code: string): boolean {
        return code === "1234"; // Упрощённая проверка
    }

    // Публичный интерфейс
    armAlarm(): void {
        if (!this.#isDoorLocked) {
            throw new Error("Нельзя включить сигнализацию при открытой двери");
        }

        this.#alarmStatus = "Armed";
        this.#updateActivity();
    }

    disarmAlarm(code: string): void {
        if (this.#validateCode(code)) {
            this.#alarmStatus = "Disarmed";
            this.#updateActivity();
        }
    }

    lockDoor(): void {
        this.#isDoorLocked = true;
        this.#updateActivity();
    }

    unlockDoor(code: string): void {
        if (this.#validateCode(code)) {
            this.#isDoorLocked = false;
            this.#updateActivity();
        }
    }

    getStatus(): string {
        const door = this.#isDoorLocked ? "Locked" : "Unlocked";
        return `Alarm: ${this.#alarmStatus}, Door: ${door}`;
    }
}
public class SecuritySystem {
    private String alarmStatus = "Disarmed";
    private boolean isDoorLocked = true;
    private LocalDateTime lastActivity;

    // Внутренний приватный метод
    private void updateActivity() {
        lastActivity = LocalDateTime.now();
        logActivity();
    }

    private void logActivity() {
        System.out.printf("Activity at %s: Alarm %s, Door %s%n",
                lastActivity, alarmStatus, isDoorLocked ? "Locked" : "Unlocked");
    }

    private boolean validateCode(String code) {
        return code.equals("1234"); // Упрощённая проверка
    }

    // Публичный интерфейс
    public void armAlarm() {
        if (!isDoorLocked) {
            throw new IllegalStateException("Нельзя включить сигнализацию при открытой двери");
        }

        alarmStatus = "Armed";
        updateActivity();
    }

    public void disarmAlarm(String code) {
        if (validateCode(code)) {
            alarmStatus = "Disarmed";
            updateActivity();
        }
    }

    public void lockDoor() {
        isDoorLocked = true;
        updateActivity();
    }

    public void unlockDoor(String code) {
        if (validateCode(code)) {
            isDoorLocked = false;
            updateActivity();
        }
    }

    public String getStatus() {
        return "Alarm: " + alarmStatus + ", Door: " + (isDoorLocked ? "Locked" : "Unlocked");
    }
}
class SecuritySystem {
    private var alarmStatus = "Disarmed"
    private var isDoorLocked = true
    private var lastActivity: LocalDateTime? = null

    private val doorState: String get() = if (isDoorLocked) "Locked" else "Unlocked"

    // Внутренние приватные методы
    private fun updateActivity() {
        lastActivity = LocalDateTime.now()
        logActivity()
    }

    private fun logActivity() {
        println("Activity at $lastActivity: Alarm $alarmStatus, Door $doorState")
    }

    private fun validateCode(code: String) = code == "1234" // Упрощённая проверка

    // Публичный интерфейс
    fun armAlarm() {
        check(isDoorLocked) { "Нельзя включить сигнализацию при открытой двери" }

        alarmStatus = "Armed"
        updateActivity()
    }

    fun disarmAlarm(code: String) {
        if (validateCode(code)) {
            alarmStatus = "Disarmed"
            updateActivity()
        }
    }

    fun lockDoor() {
        isDoorLocked = true
        updateActivity()
    }

    fun unlockDoor(code: String) {
        if (validateCode(code)) {
            isDoorLocked = false
            updateActivity()
        }
    }

    fun getStatus() = "Alarm: $alarmStatus, Door: $doorState"
}

Преимущества инкапсуляции

Преимущество Описание
Контроль над данными Предотвращение некорректных состояний объекта
Гибкость изменений Можно менять внутреннюю реализацию, не затрагивая код, который использует класс
Безопасность Защита критически важных данных от непреднамеренного изменения
Упрощение использования Пользователь класса работает с простым публичным интерфейсом
Поддержка целостности Гарантия, что объект всегда находится в валидном состоянии

Практические рекомендации

  • Сначала делайте поля приватными – поднимайте уровень доступа только при необходимости
  • Используйте свойства вместо публичных полей – это дает гибкость для будущих изменений
  • Минимизируйте публичный интерфейс – чем меньше методов и свойств доступно извне, тем проще поддерживать и изменять код
  • Добавляйте валидацию в сеттеры свойств и параметры методов
  • Используйте методы для операций, которые изменяют состояние объекта

Итоги

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

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

Помните: хороший класс подобен чёрному ящику – внешний мир знает, что он делает, но не как он это делает.