Ответ
Поддержание объекта в согласованном (консистентном) состоянии — это фундаментальный принцип объектно-ориентированного проектирования, означающий, что в любой момент времени внутреннее состояние объекта (значения его полей) должно соответствовать определённым бизнес-правилам и инвариантам класса. Объект никогда не должен переходить в «незаконное» или противоречивое состояние.
Как этого добиться?
- Строгая инкапсуляция: Состояние объекта должно быть приватным (
private). Доступ к нему — только через публичные методы и свойства (геттеры/сеттеры). - Валидация на входе: Любой метод или свойство, изменяющее состояние, должно проверять входные аргументы на корректность перед внесением изменений.
- Защита инвариантов: Инвариант — это условие, истинное для объекта на протяжении всего его жизненного цикла (например, «баланс счёта не может быть отрицательным»). Код должен гарантировать его выполнение.
- Использование конструкторов: Объект должен быть полностью инициализирован и находиться в согласованном состоянии сразу после создания.
Пример на C#:
public class BankAccount
{
// Поля приватные — состояние инкапсулировано
private decimal _balance;
private string _accountNumber;
private bool _isFrozen;
// Объект создаётся в согласованном состоянии через конструктор
public BankAccount(string accountNumber, decimal initialDeposit)
{
if (string.IsNullOrWhiteSpace(accountNumber))
throw new ArgumentException("Account number is required.");
if (initialDeposit < 0)
throw new ArgumentException("Initial deposit cannot be negative.");
_accountNumber = accountNumber;
_balance = initialDeposit;
_isFrozen = false; // По умолчанию счёт активен
}
// Свойство с валидацией в сеттере (или только геттер + методы для изменения)
public decimal Balance
{
get => _balance;
private set
{
// Инвариант: баланс не может стать отрицательным
if (value < 0)
throw new InvalidOperationException("Insufficient funds. Balance cannot be negative.");
_balance = value;
}
}
// Методы изменяют состояние, обеспечивая его согласованность
public void Deposit(decimal amount)
{
if (_isFrozen) throw new InvalidOperationException("Account is frozen.");
if (amount <= 0) throw new ArgumentException("Deposit amount must be positive.");
Balance += amount; // Используем свойство для валидации
}
public void Withdraw(decimal amount)
{
if (_isFrozen) throw new InvalidOperationException("Account is frozen.");
if (amount <= 0) throw new ArgumentException("Withdrawal amount must be positive.");
Balance -= amount; // Свойство Balance проверит, не станет ли баланс отрицательным
}
public void Freeze() => _isFrozen = true;
public void Unfreeze() => _isFrozen = false;
}
Что обеспечивает этот код?
- Целостность данных: Невозможно создать счёт с отрицательным начальным балансом или пустым номером.
- Безопасность операций: Нельзя снять деньги с замёрзшего счёта или внести отрицательную сумму.
- Соблюдение бизнес-правил: Баланс защищён от ухода в минус.
Последствия несогласованного состояния:
- Трудноуловимые баги: Объект в неверном состоянии может «всплыть» далеко от места, где произошла ошибка.
- Нарушение логики приложения: Последующие методы могут работать некорректно, получив некорректные данные.
- Проблемы с многопоточностью: Состояние может быть изменено из нескольких потоков одновременно, что требует дополнительных мер (блокировки, неизменяемость).
Вывод: Ответственность за поддержание собственного состояния в согласованном виде лежит на самом классе. Это делает код более надёжным, предсказуемым и упрощает его тестирование.