在领域对象方法中封装服务调用

6

这是一个有效的对象设计吗? 我有一个领域对象,在其中注入了一个服务,并调用验证方法来更新对象的状态,如果一切顺利,则发送确认消息。代码如下:

class Foo {
  String bar
  Service emailService


  public boolean verify() {
    bar = "foo"
        if(this.save()) {
            emailService.sendConfirmation()
        }
  }
}

Foo.get(1).verify()

这样调用emailService是否可行?有没有设计模式可以用于这种情况。

谢谢, - Ken

3个回答

11

在实体中调用服务并没有什么错。但是,在实例化这些服务方面可能会出现一些问题。如果你选择了这种方式,在实体创建过程中必须以某种方式获取一个服务的实例,这是有问题的。

直接调用构造函数显然是不好的(因为它将实体与服务实现耦合在一起)。

Jimmy Bogard 解释了为什么将服务注入实体是个坏主意

他建议使用“双重分派”(有争议是否该这样称呼)模式来解决这个问题。在这种方法中,域方法被调用者向域方法提供服务实现。在您的情况下,它看起来像这样:

class Foo {
  String bar    

  public boolean verify(Service emailService) {
    bar = "foo"
        if(this.save()) {
            emailService.sendConfirmation()
        }
  }
}

Foo.get(1).verify(new Service(...))

最后(但不是最不重要的)一个选项是使用领域事件模式。您可以在Udi Dahan的博客中了解它。在这种方法中,实体仅负责发布由适当处理程序订阅的有意义的事件。您可以在我的博客上阅读所有这些技术的全面比较。

希望对您有所帮助。


我认为你应该在这里编写一个接口。这将使你的实体可测试,并且如果需要更改服务,那么更改也会变得容易。public bool verify(IConfirmationService emailService) ... - Unmesh Kondolikar
1
我非常喜欢领域事件模式,并且很享受阅读Udi Dahan的文章,尤其是评论和他对评论的回复。我也认为当领域事件技术无法解决手头的问题时,应该使用双重分派技术。你还指出了服务定位器,我个人认为它们是邪恶的,因为它们给人一种虚假的封装感,同时也让测试变得困难。感谢你的见解和答案。 - ken

3

这样做的不好之处在于您失去了领域模型的隔离性。 您的领域模型了解纯基础设施的电子邮件服务。

引入应用程序服务可能是个好主意。 事件模式也可以解决问题。


2

通常我会在调用验证的地方发送确认邮件。如果验证的结果需要发送多个确认邮件,那么我可能会让Foo生成并返回ConfirmationMessage(一个领域对象),该对象封装了所有确认所需的信息。然后调用者可以将这些消息排队进行处理。

话虽如此,如果您的Service是接口,并且您可以注入模拟对象进行测试并在生产环境中使用真正的对象,则看起来很不错。尽管我认为最好让领域对象自己持有关于自身的知识和逻辑,而不是在另一层中与系统(服务)相关。


+1 验证对象与发送电子邮件毫无关系。想象一下,在批量操作中验证多个对象 - 每个对象都会发送一封电子邮件! - Vijay Patel
拥有一个验证方法返回ConfirmationMessage对象的API会不会很奇怪呢?API客户端期望的是操作是否成功。按照您的建议,当验证操作失败时,您会返回null值吗? - ken
@ken。是的,这会非常奇怪。最好返回一个ValidationResult,其中包含ConfirmationMessage(s),但也许这有点过度设计。我通常更喜欢在调用验证方法的操作中构造ConfirmationMessage(s)。问题在于,如果验证需要发送多个确认消息,则必须以某种方式将此信息从实体传递回操作。 - cherouvim
这说得有道理,但在这种情况下,我仍然更喜欢基于事件的解决方案,因为它在处理响应时引入了更多的灵活性,比如多重分派或将操作委托给目标组件。感谢您的见解。 - ken
是的,基于事件的解决方案似乎是最清晰的。需要花些时间研究并应用它 :) - cherouvim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接