Что такое принцип «Tell, Don't Ask»?
Смежные принципы проектирования
Tell, Don't Ask: вместо того чтобы запрашивать у объекта состояние и решать снаружи, объекту говорят, что сделать, — а решение он принимает сам, на своих данных.
Ask
if (account.getBalance() >= amount && !account.isFrozen()) {
account.setBalance(account.getBalance() - amount);
}Правило списания размазано по вызывающему коду: в пяти местах его скопируют по-разному, и одно забудет проверку заморозки.
Tell
class Account {
#balance;
#frozen;
constructor(balance, frozen = false) {
this.#balance = balance;
this.#frozen = frozen;
}
withdraw(amount) {
if (this.#frozen) throw new Error('Счёт заморожен');
if (amount > this.#balance) throw new Error('Недостаточно средств');
this.#balance -= amount;
}
}Инвариант «баланс не уходит в минус» проверяется в одном месте, снаружи его не обойти.
Что даёт
- Настоящую инкапсуляцию: поведение вместо пары геттер-сеттер на каждое поле.
- Защиту от анемичной модели (термин Фаулера): логика живёт рядом с данными.
- Меньше зацепления: вызывающему не нужно устройство объекта.
Когда спрашивать нормально
- Для отображения: UI читает баланс, чтобы его показать.
- CQS Мейера: методы-запросы без побочных эффектов законны; плохо, когда по их результату за объект принимают решение.
- В Redux данные намеренно отделены от поведения: компонент отправляет действие
withdraw, а не вычисляет новый баланс сам.
Коротко для собеседования
Решение, зависящее от состояния объекта, принимает сам объект: вызывающий говорит «спиши деньги», а не читает баланс и меняет его сам. Так инвариант защищён в одном месте; читать состояние для отображения нормально, а операции над несколькими агрегатами уходят в доменный сервис.