告诉,不要问

2013 年 9 月 5 日

告诉,不要问是一个原则,它帮助人们记住面向对象是关于将数据与操作该数据的函数捆绑在一起。它提醒我们,与其询问对象数据并对该数据进行操作,不如告诉对象该做什么。这鼓励将行为移入对象以与数据一起使用。

让我们用一个例子来澄清。假设我们需要监控某些值,如果值超过某个限制,则发出警报。如果我们以“询问”风格编写它,我们可能有一个数据结构来表示这些东西……

class AskMonitor...

  private int value;
  private int limit;
  private boolean isTooHigh;
  private String name;
  private Alarm alarm;

  public AskMonitor (String name, int limit, Alarm alarm) {
    this.name = name;
    this.limit = limit;
    this.alarm = alarm;
  }

…并将此与一些访问器结合起来以获取此数据

class AskMonitor...

  public int getValue() {return value;}
  public void setValue(int arg) {value = arg;}
  public int getLimit() {return limit;}
  public String getName()  {return name;}
  public Alarm getAlarm() {return alarm;}

然后,我们将像这样使用数据结构

AskMonitor am = new AskMonitor("Time Vortex Hocus", 2, alarm);
am.setValue(3);
if (am.getValue() > am.getLimit()) 
  am.getAlarm().warn(am.getName() + " too high");

“告诉,不要问”提醒我们,相反,将行为放在监视器对象本身内(使用相同的字段)。

class TellMonitor...

  public void setValue(int arg) {
    value = arg;
    if (value > limit) alarm.warn(name + " too high");
  }

将像这样使用

TellMonitor tm = new TellMonitor("Time Vortex Hocus", 2, alarm);
tm.setValue(3);

许多人发现告诉,不要问是一个有用的原则。面向对象设计的基本原则之一是将数据和行为结合起来,以便我们系统的基本元素(对象)将两者结合在一起。这通常是一件好事,因为这些数据和操作它们的的行为紧密耦合:一方的变化会导致另一方的变化,理解一方有助于理解另一方。紧密耦合的事物应该在同一个组件中。考虑告诉,不要问是一种帮助程序员了解如何增加这种共置的方法。

但就我个人而言,我不使用告诉,不要问。我确实希望将数据和行为共置,这通常会导致类似的结果。我发现告诉,不要问的一个问题是,我看到它鼓励人们成为GetterEradicator,试图消除所有查询方法。但是,有时对象通过提供信息有效地协作。一个很好的例子是接受输入信息并将其转换为简化其客户端的对象,例如使用EmbeddedDocument。我看到代码陷入只有告诉的卷积中,而合适的查询方法会简化问题[1]。对我来说,告诉,不要问是通往将行为和数据共置的垫脚石,但我没有发现它是一个值得强调的点。

进一步阅读

该原则最常与 Andy Hunt 和“Prag”Dave Thomas(实用程序员)相关联。他们在IEEE 软件专栏他们网站上的帖子中描述了它。

笔记

1: 事实上,即使将数据和行为共置的更基本原则有时也应该为了其他考虑因素(例如分层)而放弃。好的设计都是关于权衡的,将数据和行为共置只是需要考虑的一个因素。