Tell Don’t Ask
The Tell, Don’t Ask (TDA) principle suggests that it is better to issue an object a command do perform some operation or logic, rather than to query its state and then take some action as a result.
It is related to the Flags Over Objects antipattern as well as the Anemic Domain Modelantipattern.
You can easily spot violations of TDA in code that queries or uses several properties of an object in order to perform some calculation.
This is especially problematic when the same kind of calculation is done in many places (violating the Don’t Repeat Yourself principle), but can represent a design deficiency even if it only occurs in one location in the current codebase.
// Violates TDA
public class CpuMonitor
{
public int Value { get; set; }
} public class Client
{
public void AlertService(List<CpuMonitor> cpuMonitors)
{
foreach (var cpuMonitor in cpuMonitors)
{
if (cpuMonitor.Value > )
{
// alert
}
}
}
} // Refactored
public class CpuMonitor
{
private readonly int _alertThreshold; public CpuMonitor(int alertThreshold)
{
_alertThreshold = alertThreshold;
} public int Value { get; set; }
public bool ExceedsThreshold { get { return Value >= _alertThreshold; } }
} public class Client
{
public void AlertService(List<CpuMonitor> cpuMonitors)
{
foreach (var cpuMonitor in cpuMonitors)
{
if (cpuMonitor.ExceedsThreshold)
{
// alert
}
}
}
} // Refactored Further
public class CpuMonitor
{
private readonly int _alertThreshold;
private readonly Action<CpuMonitor> _alertAction; public CpuMonitor(int alertThreshold, Action<CpuMonitor> alertAction)
{
_alertThreshold = alertThreshold;
_alertAction = alertAction;
} public int Value { get; set; }
public bool ExceedsThreshold { get { return Value >= _alertThreshold; } } public void Sample()
{
if (ExceedsThreshold)
{
_alertAction(this);
}
}
} public class Client
{
public void AlertService(List<CpuMonitor> cpuMonitors)
{
foreach (var cpuMonitor in cpuMonitors)
{
cpuMonitor.Sample();
}
}
}
In the example above, the first refactoring looks at the magic number representing the alert threshold, and moves this concept into the monitor itself.
This might not be worth correcting if this client code were the only instance of this behavior, but if you find repetition in this kind of code, follow the Don’t Repeat Yourself principle and consolidate it into a class (in this case, CpuMonitor).
The initial refactoring is still querying each monitor and then performing some action based on that query, and so is still asking, not telling.
The final refactoring moves the responsibility for sending alerts into the monitor itself, while still avoiding tightly coupling it to any particular alert implementation.
In this way, monitor remains loosely coupled from whatever alert implementation the system might have in place.
It’s not necessary to follow the “don’t ask” part of this principle to the extreme of eliminating all access to objects’ state.
In general, it’s ok to query an object for its state, provided the information isn’t being used to make a decision related to the object.
If it is, then that decision and any corresponding behavior should most likely be moved within the object itself.
Another consequence of violating TDA is that often magic numbers or business rules end up sprinkled throughout code that references object state, rather than embedded within the object itself or passed into the object as a defined and well-named construct (such as CpuAlertThreshold in the example above).
class Program
{
static void Main(string[] args)
{
Person xiaoMing = new Person("小明");
xiaoMing.WhatToDo = "泡面";
new Bolier(, xiaoMing.DoSthUseHotWater).HeatUp(); Person daMing = new Person("大明");
daMing.WhatToDo = "洗脸";
new Bolier(, daMing.DoSthUseHotWater).HeatUp(); }
} public class Person
{
public string Name { get; private set; }
public string WhatToDo { get; set; }
public Person(string name)
{
Name = name;
} public void DoSthUseHotWater(Bolier bolier)
{
Console.WriteLine(string.Format("{0}用{1}度的水{2}", Name, bolier.WaterTemperature, WhatToDo));
}
} /// <summary>
/// 烧水壶
/// </summary>
public class Bolier
{
public int WaterTemperature { get; private set; }
private int necessaryWaterTemperature;
private Action<Bolier> completeAction; public Bolier(int necessaryWaterTemperature, Action<Bolier> completeAction)
{
this.necessaryWaterTemperature = necessaryWaterTemperature;
this.completeAction = completeAction;
} public void HeatUp()
{
while (WaterTemperature < necessaryWaterTemperature)
{
Console.WriteLine(string.Format("{0}-当前水温:{1}", DateTime.Now.ToString(), WaterTemperature));
Thread.Sleep();
WaterTemperature += ;
} completeAction?.Invoke(this);
}
}
随机推荐
- ASP 用隐藏域解决Http无状态问题
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head> < ...
- Post utf-8 请求
/// <summary> /// POST请求与获取结果 /// </summary> public static string HttpPost(string Url, s ...
- HTTP的请求方法一共有9种,有OPTIONS, HEAD, GET, POST等等(消息头有图,十分清楚)
请求方法:指定了客户端想对指定的资源/服务器作何种操作 下面我们介绍HTTP/1.1中可用的请求方法: [GET:获取资源] GET方法用来请求已被URI识别的资源.指定的资源经服务器端解析后 ...
- delphi 程序强制结束自身(两种方法都暴力)
procedure KillSelf;begin Sleep(1000); if not TerminateProcess(GetCurrentProcessId, 0) then WinExe ...
- UWP中的消息提示框(二)
在UWP中的消息提示框(一)中介绍了一些常见的需要用户主动去干涉的一些消息提示框,接下来打算聊聊不需要用户主动去干涉的一些消息提示框.效果就是像双击退出的那种提示框. 先说说比较简单的吧,通过系统To ...
- Linux ADF(Atomic Display Framework)浅析---概述
概述 因为工作关系,最近有涉及到ADF(Atomic Display Framework)相关的内容,部分内容来自互联网 ADF(Atomic Display Framework)是Google新增的 ...
- QT 那些事
一直做的是windows开发,有2个小项目想做到手机.看来看去,选择了QT.本文介绍自己的学习的一些心得,希望对你有帮助.1.选择QT2.安卓的一些事3.QML调用C++4.资源集成(比较有用,网上资 ...
- BoxedApp Packer 将您的原始应用“打包”成单个完全的可执行二进制文件
及时应对移动化趋势 在没有安装到本地硬盘驱动的情况下,使用闪存卡.网络文件夹或者可移动磁盘远程启动应用程序通常是一件令人头疼的事,你会发现,应用程序会突然会丢失文件.路径错误或者远程注册表故障等一系列 ...
- GNU自动化工具使用全过程详解,以及在线手册
[root@localhost test3]# cat add.h#ifndef _ADD_H_#define _ADD_H_ extern int add(int, int); #endif [ro ...
- Windows开机自启动位置
HKCU refers to HKEY_CURRENT_USERHKLM refers to HKEY_LOCAL_MACHINE HKCU\Software\Microsoft\Windows\Cu ...