使用贫血领域模型通常被认为是一种反模式,因为它鼓励程序员无意义地重复编写代码。下面我将简短(而琐碎)地用一个例子来阐述这个是如何产生的。我们可以通过细致的规划以及严格的编码规范来避免其发生,但是同样可以获得较好的封装。防止陷入贫血领域模型深坑的难度随项目人数呈指数级增长。
我相信所有人对面向对象都有所认识,但我却有趣地发现一些看似毫无意义的小举措却导致了最终一场大灾难。
第一步:编写贫血实体
在软件开发的某些情况下,我们会在一个领域实体之外实现一些逻辑。这可能是由于一个明确的设计决定或者,更有可能,持久类不能引用外部服务造成了不能将这段逻辑实现在领域对象的内部。把外部服务(依赖)添加到实体对象中将会造成与数据库的交互变的复杂而晦涩难懂。
02 | private final String name; |
03 | private final String emailAddress; |
05 | public User( final String name, final String emailAddress) { |
07 | this .emailAddress=emailAddress; |
10 | public String getName() { |
14 | public String getEmailAddress() { |
15 | return this .emailAddress; |
第二部:逻辑被实现在外部类中
一个开发组的成员决定他们需要一个用来操作这个实体的方法。这个方法(在我们的例子中)要调用到User对象,但它还需要用到一个User类所不知道的外
部服务。这段逻辑被实现在一个帮助类(helper)或者说一个服务类(service)的方法中,并且以某种方式协助了这个实体。这个帮助类不包含自带
的数据,并且仅仅从这个实体中获取数据、修改其状态。
01 | public class UserReminderService { |
02 | private IMailService mailService; |
03 | private IMessageGeneratorService messageGeneratorService; |
05 | public void sendReminderMessage( final IUser user) { |
06 | String reminderMessage = this .messageGeneratorService.generateReminderMessage(user.getName); |
07 | this .mailService.sendMessage(user.getEmailAddress(), reminderMessage); |
这个并不能实现在User实体中,因为我们根本无法在实体中取得邮件服务或者是消息生成器。到目前为止,这个看起来还不算很糟糕(我们很好地封装了消息的创建以及邮件发送过程),但是这仅仅是“败坏”的开始,然后马上开始让这些不警惕的开发者陷入灾难。
哪里错了呢?
UserReminderService是一个游手好闲的类(它掌管了太多其他类的活动)。消息的创建、把它发送出去这些都应该是User类自己的业务逻辑。
第三步:重复代码产生
在此期间,另一个开发者开发了一个全新的组件,同样也使用了User实体。这个新的服务被用来决定注册用户是真的用户而不是一个机器人。
1 | public class SignupVerificationService { |
2 | private IMailService mailService; |
3 | private IMessageGeneratorService messageGeneratorService; |
5 | public void sendVerificationEmail( final IUser user) { |
6 | String verificationMessage = this .messageGeneratorService.generateVerificationMessage(user.getName); |
7 | this .mailService.sendMessage(user.getEmailAddress(), reminderMessage); |
这个开发者可能会发现这个方法与之前的sendReminderMessage方法十分的相似。在这个情况下,他觉得他把验证功能与其他组件分开来的做法十分精明,看上去没有必要为这短短两行代码重用之前的实现。
哪里错了呢?
这两个方法看上去十分相似,但是又是不同的,使得开发者认为是两个不同的活动。这里有一种冗余的感觉,但还没有造成问题。