考虑一个使用JPA持久化和EJB服务层构建的Web邮件应用程序。假设我们在EJB中有一个像这样的服务方法:
public void incomingMail(String destination, Message message) {
Mailbox mb = findMailBox(destination); // who cares how this works
mb.addMessage(message);
}
这似乎是一种合理的业务方法。可以想象,邮箱对象仍将保持连接,并无缝地将更改保存回数据库。毕竟,这就是透明持久性的承诺。
邮箱对象将拥有此方法:
public void addMessage(Message message) {
messages.add(message);
}
这就是变得复杂的地方——假设我们想要其他类型的邮箱。比如,我们有一个自动回复邮件箱,可以自动回复给发送者,还有一个HelpDeskMailbox,它可以在收到每封邮件时自动打开一个帮助台工单。
最自然的做法将是扩展Mailbox类,其中AutoRespondingMailbox具有以下方法:
public void addMessage(Message message) {
String response = getAutoResponse();
// do something magic here to send the response automatically
}
问题在于我们的"邮箱 Mailbox"对象和其子类是"领域对象"(在本例中也是JPA实体)。Hibernate团队(以及许多其他人)倡导一种非依赖性的领域模型——即不依赖于容器/运行时提供的服务的领域模型。这种模型的问题在于AutoRespndingMailbox.addMessage()方法无法发送电子邮件,因为它无法访问JavaMail等服务。
对于HelpDeskMailbox来说,完全相同的问题会发生,因为它无法访问WebServices或JNDI注入以与HelpDesk系统通信。
因此,您被迫将此功能放在服务层中,如下所示:
public void incomingMail(String destination, Message message) {
Mailbox mb = findMailBox(destination); // who cares how this works
if (mb instanceof AutoRespondingMailbox) {
String response = ((AutoRespondingMailbox)mb).getAutoResponse();
// now we can access the container services to send the mail
} else if (mb instanceof HelpDeskMailbox) {
// ...
} else {
mb.addMessage(message);
}
}
在这种情况下使用instanceof是问题的第一个迹象。每次想要子类化Mailbox时都需要修改此服务类,这是另一个问题的迹象。
有没有最佳实践来处理这些情况?有些人会说Mailbox对象应该可以访问容器服务,并且可以通过一些调整来实现,但这明显是与JPA的预期用法相抵触的,因为容器在除实体外的所有地方都提供了依赖注入,清楚地表明这不是预期的用例。
那么,我们应该怎么做呢?难道我们要放弃多态性吗?我们的对象自动变成C风格的结构体,失去了大部分面向对象的好处。
Hibernate团队会说,我们应该将业务逻辑分为领域层和服务层,在领域实体中放置所有不依赖于容器的逻辑,并将所有依赖于容器的逻辑放入服务层。如果有人能给我一个不必完全放弃多态性并诉诸于instanceof等恶劣方法的示例,我可以接受这一点。