验证逻辑应该位于何处?
问题
如何最好地管理需要复杂验证逻辑的对象图的构造?出于可测试性的原因,我想保留注入的依赖关系,无所事事的构造函数。
可测试性对我来说非常重要,您的建议如何维护代码的这个属性?
背景
我有一个普通的java对象,它为我管理一些业务数据的结构:
class Pojo
{
protected final String p;
public Pojo(String p) {
this.p = p;
}
}
我想确保p是有效的格式,因为如果没有这种保证,这个业务对象真的没有意义;如果p是无稽之谈,它甚至不应该被创建。但是,验证 p 并非易事。
陷阱
实际上,它需要足够复杂的验证逻辑,以至于该逻辑本身应该完全可测试,因此我在单独的类中具有该逻辑:
final class BusinessLogic() implements Validator<String>
{
public String validate(String p) throws InvalidFoo, InvalidBar {
...
}
}
可能的重复问题
- 验证逻辑应在哪里实现?- 公认的答案对我来说是难以理解的。我读“在类的本机环境中运行”作为重言式,验证规则如何在“类的本机环境”以外的任何内容中运行?第2点,我无法摸索,所以我不能评论。
- 在哪里提供用于验证的逻辑规则?- 两个答案都表明责任在于客户/数据提供者,原则上我喜欢。但是,在我的情况下,客户端可能不是数据的创建者,因此无法对其进行验证。
- 在哪里保留验证逻辑?- 建议验证可以归模型所有,但是我发现这种方法不太适合测试。具体来说,对于每个单元测试,我需要关心所有的验证逻辑,即使我正在测试模型的其他部分 - 我永远无法完全隔离我想要测试的内容,遵循建议的方法。
到目前为止,我的想法
尽管下面的构造函数公开声明了 Pojo 的依赖项并保留了其简单的可测试性,但它是完全不安全的。这里没有什么可以阻止客户端提供一个验证器,该验证器声称每个输入都是可以接受的!
public Pojo(Validator businessLogic, String p) throws InvalidFoo, InvalidBar {
this.p = businessLogic.validate(p);
}
因此,我在一定程度上限制了构造函数的可见性,并且我提供了一个工厂方法,该方法可确保验证然后构造:
@VisibleForTesting
Pojo(String p) {
this.p = p;
}
public static Pojo createPojo(String p) throws InvalidFoo, InvalidBar {
Validator businessLogic = new BusinessLogic();
businessLogic.validate(p);
return new Pojo(p);
}
现在,我可以将 createPojo 重构为一个工厂类,这将恢复 Pojo 的“单一责任”并简化工厂逻辑的测试,更不用说不再浪费在每个新的 Pojo 上创建新的(无状态)BusinessLogic 的性能优势了。
我的直觉是,我应该停下来,征求外界的意见。我走在正确的轨道上吗?