让我们看一下可以在其中放置验证代码的选项:
- 在建设者的二传手里面。
- 里面的
build()
方法。
- 在构造的实体内部:创建实体时将在
build()
方法中调用它。
选项1使我们能够更早地发现问题,但是在某些复杂情况下,我们只能验证具有完整上下文的输入,因此,至少要在build()
方法中进行部分验证。因此,选择选项1将导致代码不一致,一部分验证在一个地方完成,而另一部分则在另一地方完成。
选项2并不比选项1差很多,因为通常在builder之前build()
(特别是在流畅的接口中)调用builder中的setter 。因此,在大多数情况下,仍然有可能及早发现问题。但是,如果生成器不是创建对象的唯一方法,则它将导致验证代码重复,因为您需要在创建对象的所有地方都使用它。在这种情况下,最合乎逻辑的解决方案是使验证尽可能靠近所创建的对象,即在其内部。这是选项3。
从SOLID的角度来看,将验证放入构建器中也违反了SRP:构建器类已经负责聚合数据以构造对象。验证是根据自己的内部状态建立合同,这是检查另一个对象状态的新职责。
因此,从我的角度来看,从设计的角度来看,不仅晚失败更好,而且在构造的实体内部而不是在构建器本身中失败也更好。
UPD:当在构建器内进行验证(选项1或2)有意义时,此评论使我想起了另一种可能性。如果构建器在要创建的对象上拥有自己的合同,这确实很有意义。例如,假设我们有一个构建器,该构建器构造具有特定内容(例如,数字范围列表)的字符串1-2,3-4,5-6
。该构建器可能具有类似的方法addRange(int min, int max)
。结果字符串对这些数字一无所知,也不必知道。构建器本身定义字符串的格式和数字的约束。因此,该方法addRange(int,int)
必须验证输入数字并在max小于min时引发异常。
就是说,一般规则是仅验证由建造者本身定义的合同。