Ruby Coercion协议第2部分

显式与隐式协议

有两种不同类型的强制协议:隐式和显式。

有些方法需要显式调用和软语义。 这些是#to_s#to_f#to_i#to_a等方法,…我们是否要将字符串转换为整数? 我们执行"23".to_i ,我们得到23

这很有用,但有时结果可能无法预测: "foo".to_i返回0 。 这种软语义可能导致细微的错误。

有时我们需要隐式但更强的语义来保证输出对于我们的业务逻辑确实有意义。 Ruby Kernel具有一组功能,可以帮助实现这种更强的强制性。

 # Explicit Coercion "23".to_i # => 23 "foo".to_i # => 0 # Unexpected # Implicit Coercion Integer(23) # => 23 Integer("foo") # => ArgumentError # It prevents bugs 

在这种情况下, Kernel #to_int收到的参数( 23"foo" )调用#to_int 。 在我们的示例中, 23之所以有效是因为Integer实现了#to_int ,而"foo"引发了一个错误,因为String没有实现该方法。

只有真正能够强迫自己变成另一种类型的对象才公开隐式协议。 这些方法是#to_str#to_hash等。

如果您需要将值对象强制转换为特定的原语,请考虑实现这些协议

使用上一篇文章中的Temperature示例,假设我们希望能够将其与其他对象相加。

 class Temperature # ... def initialize(temperature) @temperature = Float(temperature) end def to_s "Temperature: #{temperature}" end def to_f temperature end def +(other) self.class.new(temperature + other.to_f) end protected attr_reader :temperature end puts Temperature.new(37) + 5 # => "Temperature: 42.0" puts Temperature.new(37) + Temperature.new(9) # => "Temperature: 46.0" 

我们的方法#+可以接受任何实现#to_f对象。 我们第一次通过5 ,第二次通过Temperature.new(9) 。 这两个对象都实现#to_f强制协议,并且可以在此操作中使用它们。

但是这种情况呢?

 puts 5 + Temperature.new(37) # => Temperature can't be coerced into Fixnum (TypeError) 

强制#coerce强制

我们可以使用Ruby的#coerce使算术运算可交换

 class Temperature # ... def coerce(other) [self.class.new(other), self] end end puts 5 + Temperature.new(37) # => "Temperature: 42.0" 

结论

我们已经看到Ruby是一种很棒的语言 。 它附带了我们经常滥用的强大原语。 我们应该更喜欢通过强制协议保持与Ruby生态系统的兼容性,从而将这些原语封装为值对象。