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);
}
}
随机推荐
- SDP开发平台试用版上线!提供源码!!!!
SDP开发平台提供试用版!! SDP软件快速开发平台是一套面向对象的应用软件快速开发平台. 1.SDP 设计端--页面设计 通过简单的拖拉控件,或者快速生成控件,可以在几分钟快速制作一个页面:如图 通 ...
- UWP开发学习笔记2
RelativePanel控件: 用法 描述 RelativePanel.Above 设置当前element为目标element的上方 RelativePanel.AlignBottomWith 设置 ...
- oracle 12c连接pdb
12c中,如何连接pluggable database: 使用默认的service连接pdb,创建pdb之后,在监听中自动添加以pdb为名的service: 用户在cluster中创建service, ...
- C# 利用 OpenCV 进行视频捕获 (笔记)
原文:C# 利用 OpenCV 进行视频捕获 (笔记) 简介 这个项目是关于如何从网络摄像头或者视频文件(*.AVI)中捕获视频的,这个项目是用C#和OPENCV编写的. 这将有助于那些喜欢C#和Op ...
- LCID
Language Location (or type) Language ID Language tag Supported version Afar 0x1000 aa Release 9 Af ...
- Visual studio调试Web发生未能正常启动IIS express
今天调试web时,不知道怎么搞的,昨天还好好的,结果今天怎么也没法调试了.VS里报的错误是进程号为**的未能正常启动,看了下调试时IIS压根就没启动起来,没关系,看看事件管理器里发生了什么 找到个最关 ...
- qt截获html请求(继承QNetworkAccessManager和QNetworkReply)
QtWebkit加载html页面,html中会有很多的请求,比如<img id="testImg" src="http://*.jpg" width=&q ...
- Homebrew 1.0.0 发布,MacOS 上的包管理器,比如安装qt5keychain
神器,没有它不知道怎么用macos https://www.oschina.net/news/77367/homebrew-1-0-0 Mac OS X用户,qt5keychain可以使用homebr ...
- QThread多线程编程经典案例分析(三种方法,解释了为什么使用moveToThread的根本原因,即为了避免调用QThread::exec() )
传统的图形界面应用程序都只有一个线程执行,并且一次执行一个操作.如果用户调用一个比较耗时的操作,就会冻结界面响应. 一个解决方法是按照事件处理的思路: 调用 Void QApplication::pr ...
- javaweb各种框架组合案例(一):maven+spring+springMVC+jdbcTemplate
为了体现spring jdbc对于单表操作的优势,我专门对dao层做了一个抽离,使得抽离出的核心dao具有通用性.主要技术难点是对于泛型的反射.注意:单表操作中,数据库表的字段要和实体类的属性名保持高 ...