我正在阅读Sandi Metz的POODR,遇到了一个我不太理解的编码原理。这是代码:
class Bicycle
attr_reader :size, :chain, :tire_size
def initialize(args = {})
@size = args[:size] || 1
@chain = args[:chain] || 2
@tire_size = args[:tire_size] || 3
post_initialize(args)
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def post_initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
end
mb = MountainBike.new(front_shock: 4, rear_shock: 5)
puts mb.size
puts mb.chain
puts mb.tire_size
puts mb.front_shock
puts mb.rear_shock
此代码将输出其各自属性的1,2,3,4,5
。我不明白的是方法查找。
当山地自行车被实例化时,因为它没有自己的initialize
方法,它将沿着方法查找链向上移动到它的超类(Bicycle
)。但现在从那里开始,Bike似乎又回到了MountainBike的post_initialize方法。而不是继续沿着方法链向上,它怎么能回落呢?post_initialize
像initialize
一样的 ruby 关键字是否具有某种特殊功能?我可以使用其他一些 ruby 内省方法来查看正在发生的事情吗?
这里要了解的重要一点是,在此代码中:
def initialize(args = {})
# ...
post_initialize(args)
end
。post_initialize
有一个隐式接收器,self
.换句话说,这里的post_initialize(args)
等价于†self.post_initialize(args)
,self
是山地自行车的一个实例。 方法查找始终从接收方的类开始,‡ 因此查找MountainBike#post_initialize
没有问题。
† 这是一个谎言; 在隐私方面,它并不等同; private
方法不能使用显式接收器调用。
‡这也是一个谎言;它实际上从接收方的单例类开始,但随后它会尝试其类。
post_initialize
方法没有什么特别之处。 它只是子类的普通实例方法。
能够调用子类实例方法,即使在其构造函数中也是如此。查看此 irb 会议:
2.3.0 :003 > class Base
2.3.0 :004?> def initialize
2.3.0 :005?> foo
2.3.0 :006?> end
2.3.0 :007?> end
=> :initialize
2.3.0 :015 > class Derived < Base
2.3.0 :016?> def foo
2.3.0 :017?> puts 'I am foo.'
2.3.0 :018?> end
2.3.0 :019?> end
=> :foo
2.3.0 :020 > Derived.new
I am foo.
通常的方法是让子类调用super
,但我想 Sandi 建议post_initialize
一种方法要求子类提供自己的初始化,或者通过实现空方法正式拒绝这样做。(另外,子类的编写者可能会忘记称呼super
。 以下是使用超级的方法:
2.3.0 :001 > class Base
2.3.0 :002?> def initialize
2.3.0 :003?> puts 'in base'
2.3.0 :004?> end
2.3.0 :005?> end
=> :initialize
=> #<Derived:0x007fda6ba291d8>
2.3.0 :012 > class Derived < Base
2.3.0 :013?> def initialize
2.3.0 :014?> super
2.3.0 :015?> puts 'in derived'
2.3.0 :016?> end
2.3.0 :017?> end
=> :initialize
2.3.0 :018 > Derived.new
in base
in derived
=> #<Derived:0x007fda6b104b98>