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);
}
}
随机推荐
- WPF编游戏系列 之五 数据绑定
原文:WPF编游戏系列 之五 数据绑定 在上一篇通过用户控件将重复使用的控件封装为一个控件组,大大减少了C#代码数量,本篇继续对该控件组进行数据绑定,节省为每个控件赋值的工作.对于数据绑 ...
- SQL SERVER LEAD和LAG使用
示例:获取在48小时之内重复的记录 SELECT * FROM ( SELECT b.* , LAG(b.OperatorTime, , b.OperatorTime) OVER ( PARTITIO ...
- fileapi.h里的API函数(包括LockFileEx和FindFirstChangeNotification函数)
/** * This file is part of the mingw-w64 runtime package. * No warranty is given; refer to the file ...
- Linux 下 Redis 服务 Shell启动脚本
# chkconfig: 2345 10 90 # description: Start and Stop redis PATH=/usr/local/bin:/sbin:/usr/bin:/bin ...
- Solr Principal - 工作原理/机制
From http://lucene.apache.org/solr/guide/7_1/overview-of-documents-fields-and-schema-design.html The ...
- 青云QingCloud宣布完成C轮融资,金额1亿美元
本轮融资由两家人民币基金领投,蓝驰创投跟投. 企业级基础云服务商青云QingCloud正式宣布完成金额为1亿美元的C轮融资,本轮融资由两家人民币基金领投,蓝驰创投跟投. 青云QingCloud公司成立 ...
- c# 文本超长截断
根据控件大小进行截断 private void SetStringTruncat(Static.LabelEx lbl) { string oldStr = lbl.Text; if (string. ...
- redis INFO 解释
以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和统计数值. 通过给定可选的参数 section ,可以让命令只返回某一部分的信息: server 部分记录了 Re ...
- Python之二叉树Binarytree
二叉树是树的简化版,除根节点之外的所有节点都有一个父节点,任意节点都可以最多有一个左子节点和右子节点. 二叉树的遍历是非常重要的算法,主要分为深度优先遍历和广度优先遍历. 其中深度优先遍历按照访问根节 ...
- Hadoop 三剑客之 —— 集群资源管理器 YARN
一.hadoop yarn 简介 二.YARN架构 1. ResourceManager 2. NodeManager 3. ApplicationMa ...