Builder.build() return 应该是默认状态吗?
Should Builder.build() return a default state?
使用构建器模式总是有一个问题,即字段是否有默认值?我找不到明确定义的可靠来源...
问题是可读性:Car.Builder().build()
返回了什么?我总是需要检查 Builder
的具体实现以查看使用了哪些默认值。 Builder 不应该用于创建根据定义没有简单默认状态的复杂对象吗?
另一种方法是检查是否在 build()
方法中设置了所有必填字段:
fun build() : Car {
return if (doors != null && hp != null) Car(doors, hp, color) // color can be null
else throw IllegalArgumentException("Door count and HP is mandatory!")
}
...或者这被认为是不好的做法?
我不会将默认值设置为 Car
class,而是将默认值设置为具体的构建器。
class SedanBuilder {
var doors = 4
var wheels = 4
var driver = null // we have to set driver
... setters ...
fun build() {
return Car(wheels, doors, driver)
}
}
另一个构建器可以使用另一个默认值
class SchumachersCarBuilder {
var doors = 4
var wheels = 4
var driver = Person("Michael")
... setters ...
fun build() {
return Car(wheels, doors, driver)
}
}
当然,您必须检查 Car
构造函数中的所有强制参数
您没有找到问题的一般答案,因为 none。这取决于使用构建器的上下文或构建器正在构建的 object 的详细信息。
有时 return 默认值 object 非常有意义,其中所有成员都被初始化为默认值。但是这个默认值 object 必须是有效的。例如。要构建一个记录器 object,return 一个使用默认格式和默认日志级别记录到控制台的记录器将是有效的。有人可以立即使用它。
但有时纯粹的默认 object 没有意义。创建一个完全配置的默认 HTTP 客户端是不明智的,因为它不会提供最低预期的行为,因为目标 URL 可能是意想不到的。在这种情况下,您可以为构建器 class 编写一个构造函数,它将 URL 作为参数(可能还有一些数据 object),然后使用默认值预配置客户端 object值(例如默认请求 header、默认超时、默认缓冲区大小等)。这个 object 将满足最低使用期望。 setter 的每个后续调用都会覆盖默认值,其中每个 setter 都应在接受参数之前检查参数的有效性。
但是您应该始终在可能的情况下尝试使用默认值而不是异常值。当 object 的构造需要强制性信息时,则通过将它们放入构造函数来制作此 public。这样您就可以确保 object 始终处于有效且有用的状态。对于 HTTP 客户端构建器,如果 URL 格式错误,您可以抛出异常,以提示开发人员他构造 URL 的代码可能存在缺陷。也许阅读 Best practices for exceptions。
考虑到编写构造函数以收集所有必需参数(无法设置为有用的默认值)的解决方案,您的 build()
finalize 方法可以而且应该始终且在任何时候 return 有效且有用 object。这使您的构建器很方便。 (否则构建器的用户将被迫阅读文档以了解要调用哪些 setter。每个人都知道你不喜欢 编写 文档。每个人都知道您不喜欢在使用某些 class 之前 阅读 文档。每个人都有同感)。
使用构建器模式总是有一个问题,即字段是否有默认值?我找不到明确定义的可靠来源...
问题是可读性:Car.Builder().build()
返回了什么?我总是需要检查 Builder
的具体实现以查看使用了哪些默认值。 Builder 不应该用于创建根据定义没有简单默认状态的复杂对象吗?
另一种方法是检查是否在 build()
方法中设置了所有必填字段:
fun build() : Car {
return if (doors != null && hp != null) Car(doors, hp, color) // color can be null
else throw IllegalArgumentException("Door count and HP is mandatory!")
}
...或者这被认为是不好的做法?
我不会将默认值设置为 Car
class,而是将默认值设置为具体的构建器。
class SedanBuilder {
var doors = 4
var wheels = 4
var driver = null // we have to set driver
... setters ...
fun build() {
return Car(wheels, doors, driver)
}
}
另一个构建器可以使用另一个默认值
class SchumachersCarBuilder {
var doors = 4
var wheels = 4
var driver = Person("Michael")
... setters ...
fun build() {
return Car(wheels, doors, driver)
}
}
当然,您必须检查 Car
构造函数中的所有强制参数
您没有找到问题的一般答案,因为 none。这取决于使用构建器的上下文或构建器正在构建的 object 的详细信息。
有时 return 默认值 object 非常有意义,其中所有成员都被初始化为默认值。但是这个默认值 object 必须是有效的。例如。要构建一个记录器 object,return 一个使用默认格式和默认日志级别记录到控制台的记录器将是有效的。有人可以立即使用它。
但有时纯粹的默认 object 没有意义。创建一个完全配置的默认 HTTP 客户端是不明智的,因为它不会提供最低预期的行为,因为目标 URL 可能是意想不到的。在这种情况下,您可以为构建器 class 编写一个构造函数,它将 URL 作为参数(可能还有一些数据 object),然后使用默认值预配置客户端 object值(例如默认请求 header、默认超时、默认缓冲区大小等)。这个 object 将满足最低使用期望。 setter 的每个后续调用都会覆盖默认值,其中每个 setter 都应在接受参数之前检查参数的有效性。
但是您应该始终在可能的情况下尝试使用默认值而不是异常值。当 object 的构造需要强制性信息时,则通过将它们放入构造函数来制作此 public。这样您就可以确保 object 始终处于有效且有用的状态。对于 HTTP 客户端构建器,如果 URL 格式错误,您可以抛出异常,以提示开发人员他构造 URL 的代码可能存在缺陷。也许阅读 Best practices for exceptions。
考虑到编写构造函数以收集所有必需参数(无法设置为有用的默认值)的解决方案,您的 build()
finalize 方法可以而且应该始终且在任何时候 return 有效且有用 object。这使您的构建器很方便。 (否则构建器的用户将被迫阅读文档以了解要调用哪些 setter。每个人都知道你不喜欢 编写 文档。每个人都知道您不喜欢在使用某些 class 之前 阅读 文档。每个人都有同感)。