tell-dont-ask

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Tell Don't Ask

Tell Don't Ask原则

Core Principle

核心原则

Tell Don't Ask (TDA) means: instead of asking an object for its data and then acting on it externally, tell the object what to do and let it use its own data internally.
This flows directly from OOP's core idea — bundle data with the behavior that operates on it. Tightly coupled data and behavior belong in the same component.

**Tell Don't Ask(TDA)**的含义是:不要询问对象获取其数据然后在外部对其操作,而是告知对象要做什么,让它在内部使用自己的数据来完成。
这直接源自OOP的核心思想——将数据与操作数据的行为绑定在一起。紧密耦合的数据和行为应该放在同一个组件中。

Identifying "Ask" Style (The Problem)

识别「询问」风格(问题所在)

Watch for this pattern: external code queries an object's state and then makes decisions based on that state.
ts
if (order.status === "pending" && order.total > 1000) {
    order.approvalQueue.push(order.id);
    order.status = "awaiting_approval";
}
Red flags:
  • Chains of getters used to make a decision
  • if (obj.getX() > obj.getLimit())
    patterns outside the class
  • External code setting state after reading state (
    get
    then
    set
    )
  • Logic that "belongs" to an object scattered across callers

留意这种模式:外部代码查询对象的状态,然后基于该状态做出决策
ts
if (order.status === "pending" && order.total > 1000) {
    order.approvalQueue.push(order.id);
    order.status = "awaiting_approval";
}
危险信号:
  • 用于做决策的一连串getter调用
  • 在类外部出现
    if (obj.getX() > obj.getLimit())
    这类模式
  • 外部代码先读取状态再设置状态(先
    get
    set
  • 本该属于某个对象的逻辑分散在多个调用方中

Applying the Refactor

应用重构方案

Move the decision-making logic into the object that owns the data.
ts
order.submitForApproval();
ts
class Order {
  submitForApproval(): void {
    if (this.status === "pending" && this.total > 1000) {
      this.approvalQueue.push(this.id);
      this.status = "awaiting_approval";
    }
  }
}
The caller tells the object what to do; the object decides how.

将决策逻辑移入拥有该数据的对象内部。
ts
order.submitForApproval();
ts
class Order {
  submitForApproval(): void {
    if (this.status === "pending" && this.total > 1000) {
      this.approvalQueue.push(this.id);
      this.status = "awaiting_approval";
    }
  }
}
调用者告知对象要做什么;对象自己决定具体怎么做。

Step-by-Step Refactoring Guide

分步重构指南

  1. Find the ask chain — locate external code that reads object data to make a decision.
  2. Identify which object owns the data — the class whose fields are being read.
  3. Name the intent — what is the caller trying to accomplish? Name a method for that intent (e.g.,
    submitForApproval
    ,
    checkAlarm
    ,
    applyDiscount
    ).
  4. Move the logic in — cut the conditional/action block and paste it into the new method on the owning class.
  5. Replace the call site — swap the ask chain with a single tell call.
  6. Remove now-unnecessary getters if nothing else uses them (be cautious — see nuance below).

  1. 找到询问链——定位那些读取对象数据来做决策的外部代码。
  2. 确定数据所属对象——即被读取字段的所属类。
  3. 明确意图——调用者实际想要完成什么?为这个意图命名一个方法(例如
    submitForApproval
    checkAlarm
    applyDiscount
    )。
  4. 移入逻辑——将条件判断/操作块剪切并粘贴到所属类的新方法中。
  5. 替换调用代码——将询问链替换为单个告知式调用。
  6. 移除不再需要的getter(如果没有其他地方使用的话)——操作时需谨慎,详见下文的注意事项。

Nuance: When Getters Are Fine

注意事项:何时Getters是合理的

Martin Fowler himself notes he doesn't strictly follow TDA. Query methods are legitimate when:
  • An object transforms data for its caller (e.g., formatting, aggregation)
  • The object is a value object or DTO whose purpose is to carry data
  • You're crossing architectural layers (e.g., persistence, serialization)
  • Removing a getter would create convolutions worse than the query
The goal is co-locating behavior with data, not eliminating all accessors. Don't become a "getter eradicator" — use judgment.

Martin Fowler本人曾提到,他并不会严格遵循TDA原则。在以下场景中,查询方法是合理的:
  • 对象为调用者转换数据时(例如格式化、聚合)
  • 对象是值对象或DTO,其用途就是承载数据
  • 架构层时(例如持久化、序列化)
  • 移除getter会导致比查询更复杂的问题时
我们的目标是将行为与数据放在一起,而不是消除所有访问器。不要成为「getter清除者」——要根据实际情况判断。

Quick Reference Examples

快速参考示例

Ask (avoid)Tell (prefer)
if (a.value > a.limit) a.alarm.warn(...)
a.setValue(newVal)
— alarm logic inside
setValue
if (cart.items.length === 0) showEmpty()
if (cart.isEmpty()) showEmpty()
— at minimum, encapsulate the check
order.status = order.computeNextStatus()
order.advance()

询问(应避免)告知(推荐)
if (a.value > a.limit) a.alarm.warn(...)
a.setValue(newVal)
—— 告警逻辑在
setValue
内部
if (cart.items.length === 0) showEmpty()
if (cart.isEmpty()) showEmpty()
—— 至少要封装检查逻辑
order.status = order.computeNextStatus()
order.advance()

When Reviewing Code

代码审查时的要点

For each class under review:
  1. List all getters — are any used only to make external decisions about the object?
  2. Find conditional blocks that read from a single object's fields — can they move in?
  3. Look for setter chains (
    setA
    ,
    setB
    ,
    setC
    in sequence) — could that be one
    configure(...)
    or
    initialize(...)
    call?
Flag violations clearly, suggest the encapsulated alternative, but note if the refactor would be overkill (e.g., trivial DTOs, framework-required accessors).
对于每个被审查的类:
  1. 列出所有getter——是否有仅被用于在外部对对象做决策的?
  2. 查找读取单个对象字段的条件代码块——这些逻辑能否移入该对象内部?
  3. 查找连续的setter调用(连续调用
    setA
    setB
    setC
    )——能否替换为一个
    configure(...)
    initialize(...)
    调用?
明确标记违规之处,建议使用封装后的替代方案,但如果重构会得不偿失(例如简单的DTO、框架要求的访问器),也需注明。