来自Google测试博客的文章:使用领域对象编写可适应变化的代码
尽管产品的需求可能经常变化,但其基本理念通常变化缓慢。这导致一个有趣的见解:如果我们编写的代码符合产品的基本理念,它将更有可能在未来的产品变化中生存下来。
领域对象是我们代码中的构建块(例如类和接口),它们与产品的基本思想相匹配。我们不是编写代码来匹配产品要求的期望行为(,而是匹配底层思想。
例如,假设您是 gPizza 团队的一员,该团队出售美味、新鲜的披萨来满足饥饿的 Google 员工的需求。由于需求旺盛,您的团队决定增加送货服务。
如果没有域对象,那么实现披萨外送的最快途径就是简单地创建一个deliverPizza方法:
public class DeliveryService{ |
虽然这种方法一开始效果不错,但如果 gPizza 将其产品扩展到其他食品,会发生什么情况?您可以添加一种新方法:
public void deliverWithDrinks( L[b]ist<Pizza> 披萨, List<Drink> 饮料)[/b] { ... } |
但随着需求列表的增加(零食、糖果等),您将不得不添加越来越多的方法。如何更改初始实现以避免这种持续的维护负担?
您可以添加一个域对象来模拟产品的想法,而不是它的需求:
- 用例是帮助产品满足其业务需求的特定行为。(在本例中为“送披萨,这样我们就能赚更多的钱” 。)
- 领域对象表示几个类似用例所共享的共同想法。
要确定适当的域对象,请问自己:
- 该产品支持哪些相关用例,以及我们计划在未来支持什么?
答:gPizza 现在想要提供披萨外送服务,最终还会提供饮料和小吃等其他产品。
- 这些用例有哪些共同的想法?
A:gPizza 希望将顾客订购的食物发送给他们。
- 我们可以使用什么样的领域对象来表示这个共同的想法?
答:领域对象是一份食品订单。我们可以将用例封装在FoodOrder类中。
域对象可能是一种有用的泛化 - 但请避免选择过于通用的对象,因为在提高可维护性和更复杂、更模糊的代码之间存在权衡。通常,目标是仅支持计划中的用例 - 而不是所有可能的用例(参见YAGNI原则)。
// 好: 我们的成果显而易见。 |
在Eric Evans 所著的《领域驱动设计》一书中了解有关领域对象和领域驱动设计的更高级主题的更多信息。
banq注:
deliver的方法参数进一步是一种Command,是下了FoodOrder的命令,并不真正是FoodOrder本身,FoodOrder产生取决于deliver服务内部的业务逻辑,用户下了食品订单,但是系统是否接受和处理,是另外一回事。