Введение
О сокрытии полей мы говорили уже не раз: и когда разбирали, почему пробег меняет только
метод 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"
}
Преимущества инкапсуляции
| Преимущество | Описание |
|---|---|
| Контроль над данными | Предотвращение некорректных состояний объекта |
| Гибкость изменений | Можно менять внутреннюю реализацию, не затрагивая код, который использует класс |
| Безопасность | Защита критически важных данных от непреднамеренного изменения |
| Упрощение использования | Пользователь класса работает с простым публичным интерфейсом |
| Поддержка целостности | Гарантия, что объект всегда находится в валидном состоянии |
Практические рекомендации
- Сначала делайте поля приватными – поднимайте уровень доступа только при необходимости
- Используйте свойства вместо публичных полей – это дает гибкость для будущих изменений
- Минимизируйте публичный интерфейс – чем меньше методов и свойств доступно извне, тем проще поддерживать и изменять код
- Добавляйте валидацию в сеттеры свойств и параметры методов
- Используйте методы для операций, которые изменяют состояние объекта
Итоги
Инкапсуляция – это решение проектировщика, а не возможность языка. Ключевые слова, регистр первой буквы, подчёркивание в имени – всё это лишь способы записать одно и то же решение: что относится к устройству объекта, а что к его обещаниям внешнему коду.
Отсюда и практическая польза. Пока граница проведена, внутренности можно переписать, не трогая тех, кто объектом пользуется; некорректное состояние возникает в одном месте, а не в любой точке программы; а читать чужой класс получается по короткому списку публичных методов, а не по всему коду целиком.
Помните: хороший класс подобен чёрному ящику – внешний мир знает, что он делает, но не как он это делает.