域对象属性和封装

时间:2010-09-03 06:55:56

标签: design-patterns domain-driven-design encapsulation

我有一个带有两个属性,标签和值的Email对象。系统用户需要先验证他们的电子邮件,然后才能在系统中使用它。验证过程非常简单:

  1. 设置电子邮件的激活码
  2. 发送包含激活码的电子邮件,以验证电子邮件是否有效
  3. 电子邮件对象如下所示:

    class Email {
        String label
        String value
        EmailStatus status
        String activationCode
    
        boolean requestVerification() {
            // Set the activationCode that will be refereced to change the email status 
            this.activationCode = UUID
            // Raise an event to send a notification email by a communication service
            EventManager.fire('emailVerificationRequest',this)
        }
    

    一切正常,但在Email对象中感觉不到activationCode属性。它无论如何都没有描述对象的状态,它只在电子邮件验证过程中使用。所以我修改了我的代码以引入一个名为ActivationToken的新对象。该对象将用于封装activationCode。这是电子邮件对象的新版本

     class Email {
        String label
        String value
        EmailStatus status
    
        boolean requestVerification() {
            new ActivationToken(target:this,code:UUID,expiryDate:new Date()).save()
            // Raise an event to send a notification email by a communication service
            EventManager.fire('emailVerificationRequest',this)
        }
    
     class ActivationToken {
        Email target
        String code
        Date expiryDate
     }
    
    1. 这是一个合理的域设计,还是我的对象一无所获
    2. requestVerification方法首先属于Email对象,还是应该放在别处;在服务中或在流程中。
    3. 我是否有任何设计模式可以解决类似的问题
    4. 更新

      我想解释一下为什么即使在第二次重构方法之后我也将requestVerfication方法保留为Email域对象的一部分。

      我有一个远程接口,它通过调度程序以下列方式直接与域对象进行交互:

      remote://email/6/do/requestVerification
      

      最初我想保持与后端通过域对象进行的所有通信,如果需要进行交互,比如说,我正在使用IOC将其注入域对象并使用域对象作为服务代理人。远程接口和域对象之间的分离看起来很干净,但结果却是一个非常糟糕的主意,因为它使域设计失效并将无用的依赖性引入与行为无关的外部对象(在本例中为EmailVerificationService)或域对象的状态方面

      解决此问题的另一个解决方案可能是将requestVerification方法保留在自然所属的服务中,并为通信协议引入新语法,例如:

       remote://service/email/do/requestVerification
      

      你们有什么想法?

      谢谢

      -Ken

3 个答案:

答案 0 :(得分:1)

就个人而言,我发现这两种方法都可以接受,但更喜欢第二种方法。我将用什么来确定哪种方法,将是域专家的输入。在对域进行建模时,激活码是否明确属于需求的一部分,或者您是否有理由在验证电子邮件帐户后对其进行维护?如果你没有明确的理由采用第一种方法,我会坚持你的第二种方式。

关于您的个人问题:

  1. 我不会认为它使您的对象变得复杂,更多的是从域中提取服务级别的问题。是的,这是更多的代码,更多的工作,但可能是更好的方法 - 除非你有明确的理由使用原始方法。

  2. 如上所述,我认为这是服务级别的责任,应该在某种EmailVerificationService中。在域模型本身中,您只关心如果电子邮件是有效的,而不是验证它的有效方式。

  3. 在我看来,你已经在使用最好的方法了。从域对象引发事件的事件总线干净可靠。

答案 1 :(得分:0)

您已采取正确的步骤将电子邮件 ActivationToken 分开 - 从概念上讲,它们是分开的东西(顺便说一句,我会使用 EmailActivationToken 为了清楚起见。)

通常, EmailVerificationService 将用于封装验证逻辑。

答案 2 :(得分:0)

我认为将功能封装在ActivationToken中是个好主意。但是,通过在ActivationToken类中初始化Email,您已经创建了对外部资源的隐藏依赖项。这有效地使Email难以进行单元测试,并被具有另一种激活方案的其他客户端重用。

相反,您可能希望使用Dependency Injection/Inversion of Control模式将ActivationToken注入Email类。这允许您注入不同的激活策略并打开,以便轻松对Email类进行单元测试。

要执行此操作,您需要ActivationToken的接口,Email类的构造函数应该引用实现此接口的对象:

interface IActivationToken
{
   void Save(object target, string code, DateTime date);
}

class Email
{
    IActivationToken token;
    // Other properties go here.

    public Email(IActivationToken token)
    {
        this.token = token;
    }

    boolean RequestVerification() 
    {    
        token.Save(this, code, date);        
        // Raise an event to send a notification email by a communication service    
        EventManager.fire('emailVerificationRequest', this)    
    }    
}

现在Email类没有对外部资源的隐藏依赖。