我一直在线阅读/离线阅读有关域驱动设计的一般验证和业务规则的位置。我无法理解的是,实体如何提供执行验证和业务规则的方法,而无需借助静态方法或拥有服务?这对于域对象不需要实例化的情况尤为重要,但我们需要验证最终用于设置对象属性的值。
我注意到诸如http://lostechies.com/jimmybogard/2007/10/24/entity-validation-with-visitors-and-extension-methods/之类的博客帖子依赖于.NET的特定扩展方法,这在Java等编程语言中是不可用的。我个人不喜欢静态方法,因为它们不能被覆盖且难以测试。
无论如何,我可以在没有静态方法的情况下执行此操作,或者只是为了使用其验证和业务规则方法来实例化不必要的域对象。如果不是,这是否意味着域驱动设计非常依赖于静态方法?
由于
答案 0 :(得分:2)
使用ValueObjects 不实体。
在注册的情况下,可以引入UserName值对象。收到注册时创建用户名对象。在UserName的构造函数中实现验证。
有关更多详细信息,请参阅此question和此presentation。
<强> EDIT1:强>
1.如何处理针对不同上下文应用不同验证规则的情况。
例如:用户名不能包含某些类型成员的号码,但是其他类型的成员需要这些号码?
也许不同的工厂方法可以做到这一点。例如UserName.forGoldenCardMember(...)或UserName.forPlainMember(...)。或者使MemberType(可能是一个层次结构)来验证UserName。
另一种替代解决方案是使用AggregateFactory(在本例中为AccountFactory)。
2.构造函数是唯一放置验证码的地方吗?我确实在网上阅读了两个观点:一个对象必须始终有效而不是永远。两者都提出了好的论据,但是还有其他方法吗?
我个人更喜欢有效的方法。传递一个可能无效的值对象会损害封装性。
<强> EDIT2:强>
要求
a)基于上下文的验证业务规则(成员类型的不同用户名规则)
b)继续验证所有业务规则,即使其中一个业务规则失败
通过使用Value Object(这种情况下的MemberType)坚持单一责任原则。 可以引入AggregateFactory来简化应用程序层(更粗糙的粒度)。
class AccoutFactory {
Account registerWith(Username username, MemberType type, ....) {
List<String> errors = new ArrayList<String>();
errors.addAll(type.listErrorsWith(username));
errors.add(//other error report...
if (CollectionUtils.isEmpty(errors)) {
return new Account(username,....);
} else {
throw new CannotRegisterAccountException(errors);
}
}
}
<强> EDIT3:强> 对于评论中的问题 a)Username对象不应该是具有返回错误的方法的对象 listErrorsWith()?毕竟,对于不同的成员类型,用户名是否有不同的规则?
我们可以从另一个角度检查这个问题:MemberTypes对用户名有不同的规则。这可能会使用多态替换Username.listErrosWith(String,MemeberType)中的if / else块;
b)如果我们在MemberType中有方法,那么知识将不会被封装在Username中。另外,我们正在讨论确保Username始终有效。
我们可以定义没有MemberType规则的用户名的有效性。假设“hippoom@stackoverflow.com”是一个有效的用户名,它是GoldenCard会员的一个很好的候选人,但对SilverCard会员不利。
c)我仍然无法看到执行验证如何返回错误列表而不从构造函数或静态方法抛出的异常中获取列表。两者看起来都不理想恕我直言。
是的,listErrorsWith()的签名:List看起来很平常,我宁愿使用没有返回值的validate(用户名)(失败时抛出异常)。但这将迫使cilent捕获每个验证步骤以同时运行验证。
答案 1 :(得分:1)