byte 拆装
public class ShoppingCart {
private final List<Item> items;
public ShoppingCart() {
items = new ArrayList<Item>();
}
public void addItem(Item item) {
items.add(item);
}
public double calcTotalCost() {
double total = 0.0;
for (Item item : items) {
total += item.getPrice();
}
return total;
}
}
…这是测试用例:
@Test
public void calculateTotalCost() {
ShoppingCart instance = new ShoppingCart();
Item a = new Item("gloves", 23.43);
instance.addItem(a);
Item b = new Item("hat", 10.99);
instance.addItem(b);
Item c = new Item("scarf", 5.99);
instance.addItem(c);
double totalCost = instance.calcTotalCost();
assertEquals(40.41, totalCost, 0.0001);
}
我不得不问,TDA是否全部归结为某种语言和语义。 考虑上面的示例,客户是在说还是在问? 尽管此代码比我的“ 问不告诉”示例要好得多,但我认为可以说客户要求总价。 请考虑以下问题:
- 告诉购物车返回总价时,如何知道您不是在查询对象的内部状态? 查看ShoppingCart代码,您可以看到总成本不是对象直接内部状态(1)的一部分,但是对象进行了计算,因此总价格是对象派生内部状态的一部分,并且这将返回给呼叫者,召集者。
- 在TDA世界中,客户为什么需要总成本? 弄清楚是否需要增加运输费用? 这可以通过ShoppingCart完成。 要将帐单提交到适当的付款系统? 同样,可以通过购物车来完成。
如果您同意返回值反映对象的内部状态(无论是直接还是推断的),那么正如Spock先生所说,逻辑指示您必须得出结论,所有方法签名都将具有无效的返回值,永远不要抛出异常,并且自己处理所有错误,看起来像这样:
public void processOrder(String arg1, int arg2);
这就是一些逻辑可以开始分解的地方。 联系Visa或Mastercard付款系统的ShoppingCart不会违反单一责任原则(SRP)吗? 还需要通知Warehouse可以装运订单。 那是ShoppingCart的责任吗? 如果我们要打印PDF格式的明细帐单怎么办? 您可以告诉ShoppingCart自己打印,但是我们是否通过在ShoppingCart中添加打印代码来打破SRP,但可能会很小? 也许这需要进一步思考,所以请看下面的通讯图:
此图显示了将商品添加到ShoppingCart的简单方案。 TDA在这里可以完美地工作,因为没有分支,没有决策,整个过程是线性的:浏览器向OrderController发送电话以添加商品,而OrderController向ShoppingCarart电话以向自己添加商品。
现在,看看下一个场景。 用户在这里确认订单,然后坐下来等待它的交付。
如果您查看该图,您会发现浏览器会TELLS OrderController确认订单。 该OrderController告诉我的购物确认订单。 ShoppingCart会致电Visa系统以将总金额记入用户卡中。 如果付款顺利,则Visa卡会致电仓库,以运送订单。
现在,如果您接受ShoppingCart负责维护用户想要的商品清单并为这些商品付款的工作,那么这可以作为一种设计。 您还必须接受Visa卡负责将其运送给客户:对我来说,所有这一切都没有意义,因为它破坏了SRP,在我看来,SRP是该卡的基本特征之一好的软件设计。 此外,当我在超市购物时,我不要求购物车付款,我必须拿出钱包。 如果进一步进行类比,那么您将得出结论,整理交易流是另一个对象的责任。 OrderController的 ?
在此最终图中,您可以看到浏览器通过TELLS OrderController确认订单。 OrderController询问总费用中的ShoppingCart ,然后通知Visa对象支付账单,最后,如果Visa成功返回,则通知Warehouse运送ShoppingCart 。 对我来说,这种设计更有意义,它告诉我们不要实用主义。
现在不要误会我的意思,我喜欢告诉别人不要问它完全合理的想法,但是它并不完美,而且可能会绊倒。 如果您在Internet上搜索示例,通常会发现它们本质上是线性的,反映出上面的前两个图,其中A调用B,B调用C,C调用D等。这并不能反映大多数应用程序。在执行程序时,您不得不向对象询问数据,并根据该数据做出决定。 告诉别问的绊脚石的第二个原因是,很容易陷入甚至不考虑对象就向对象索要数据的陷阱。 例如,看下面的代码片段:
AnnotationChecker checker = new AnnotationChecker(clazz);
if (checker.isMyComponent()) {
Object obj = clazz.newInstance();
result.put(clazz, obj);
}
从我的github 依赖项注入工厂示例项目中摘录的该示例向我展示了询问对象的状态并使用该信息来做出决定。 程序性编程又来袭了……
(1)直接内部状态:保存在对象的实例变量中的值。 派生或推断的内部状态:由对象返回的值,该值是根据该对象的实例变量计算或派生的。
参考:从我们的JCG合作伙伴 Roger Hughes 拆装中告诉“不要问” 在Captain Debug的Blog中 。
翻译自: https://www.javacodegeeks.com/2012/03/disassembling-tell-dont-ask.html
byte 拆装