对象验证:推迟还是不推迟?

前面说过 ,构造函数必须是无代码的,并且除了属性初始化外什么也不能做。 从那时起,最常被问到的问题是:参数验证如何? 如果它们“损坏”,那么在“无效”状态下创建对象的意义何在? 这样的对象稍后将在意外的时刻失败。 在实例化的瞬间抛出异常不是更好吗? 要快速失败,可以这么说吗? 这就是我的想法。

《冲Kong小丑》(2009),格雷戈里·维恩斯(Gregori Viens)

让我们从以下Ruby代码开始:

class Users {
  def initialize(file)
    @file = file
  end
  def names
    File.readlines(@file).reject(&:empty?)
  end
}

我们可以使用它从文件中读取用户列表:

Users.new('all-users.txt').names

有很多滥用此类的方法:

  • nil而不是文件名传递给ctor;
  • 传递其他东西,不是String
  • 传递一个不存在的文件;
  • 传递目录而不是文件。

您看到我们可以犯的这四个错误之间的区别吗? 让我们来看看我们的班级如何保护自己免受他们的侵害:

class Users {
  def initialize(file)
    raise "File name can't be nil" if file.nil?
    raise 'Name must be a String' unless file.is_a?(String)
    @file = file
  end
  def names
    raise "#{@file} is absent" unless File.exist?(@file)
    raise "#{@file} is not a file" unless File.file?(@file)
    File.readlines(@file).reject(&:empty?)
  end
}

前两个潜在的错误在构造函数中被滤除,而后两个潜在的错误在方法中被滤除。 我为什么要这样做? 为什么不将它们全部放入构造函数中?

因为前两个危害对象状态 ,而其他两个危害对象的运行时行为。 您还记得一个对象是它封装的一组其他对象(称为属性)的代表。 类Users的对象不能表示 nil或数字。 它只能代表名称类型为String的文件。 另一方面,该文件包含的内容以及它实际上是否为文件,不会使状态无效。 这只会给行为造成麻烦。

即使差异看起来很细微,也很明显。 与封装的对象有两个交互阶段: connectingtalking

首先,我们封装file并希望确保它确实是文件。 我们尚未讨论它,我们还不希望它为我们工作,我们只是想确保它确实我们可以在不久的将来与之对话的对象。 如果是nilfloat ,那么将来肯定会有问题。 这就是为什么我们从构造函数中引发异常。

然后是第二阶段,我们将控制权委派给对象并期望其行为正确。 在此阶段,我们可能还有其他验证程序,以确保我们的交互将顺利进行。 重要的是要提到这些验证是非常实际的情况。 我们可能会多次调用names() ,并且每次磁盘上的文件都有不同的情况。 首先,它可能不存在,但几秒钟后便可以使用并可以读取了。

理想情况下,编程语言应提供用于第一类验证的工具,例如使用严格类型的验证。 例如,在Java中,我们不需要检查file的类型,编译器会更早地捕获该错误。 由于Kotlin具有Null安全功能,因此我们可以摆脱NULL检查。 Ruby不如那些语言强大,这就是为什么我们必须“手动”验证的原因。

因此,总而言之,在构造函数中进行验证不是一个坏主意,只要验证不涉及对象,而仅确认它们足够好以供以后使用。

翻译自: https://www.javacodegeeks.com/2018/05/object-validation-defer-not.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值