深入探索

子类中的 protected 和 private

调用父类和子类的对象上的方法时,适用相同的访问规则。也就是说,当你传递给一个方法一个对象(作为一个参数),该对象与接收者对象(即方法所属的对象)具有相同的类,参数对象可以调用类的 public 和 protected 方法,但不能调用其 private 方法。

protected.rb

有关此示例,请查看 protected.rb 程序。在这里,我创建了一个名为 myob 的 MyClass 对象和一个 MyOtherClass 对象 myotherob,并且 MyOtherClass 继承自 MyClass。我尝试将 myotherob 作为参数传递给 myob 的公共方法,shout

  1. myob.shout( myotherob )

但是 shout 方法在参数对象上调用 private 方法 priv

  1. def shout( anOb ) # calls a private method
  2. puts( anOb.priv( "This is a #{anOb.class} - hurrah" ) )
  3. end

这将不能运行!Ruby 解释 priv 方法是私有的。

同样,如果我反过来这样做 - 也就是说,通过将父类对象 myob作为参数传递,并在子类对象上调用方法 shout,我会得到同样的错误:

  1. myotherob.shout( myob )

MyClass 类还有另一个公共方法,exclaim。这次会调用一个 protected 方法,prot

  1. def exclaim( anOb ) # calls a protected method
  2. puts( anOb.prot( "This is a #{anOb.class} - hurrah" ) )
  3. end

现在,我可以将 MyClass 对象 myob 或 MyOtherClass 对象 myotherob 作为参数传递给 exclaim 方法,并且在调用 protected 方法时都不会发生错误:

  1. myob.exclaim( myotherob ) # This is OK
  2. myotherob.exclaim( myob ) # And so is this…

不用说,这仅在两个对象(接收器和参数)共享相同的继承链时才有效。如果发送不相关的对象作为参数,则无论其保护级别如何,你都无法调用接收器对象所属类的方法。

突破 private 方法的隐私限制

私有方法的重点在于它不能从它所属的对象作用域之外被调用。所以这将不起作用:

send.rb
  1. class X
  2. private
  3. def priv( aStr )
  4. puts("I'm private, " << aStr)
  5. end
  6. end
  7. ob = X.new
  8. ob.priv( "hello" ) # This fails

然而,事实证明 Ruby 以一种叫做 send 方法的形式提供了一个’get out’子句(或者我应该说’get in’子句?)。send 方法调用方法名称与符号(一个以冒号开头的标识符,例如 :priv)相匹配的方法,该方法名称作为第一个参数传递给 send,如下所示:

  1. ob.send( :priv, "hello" ) # This succeeds

符号后面提供的任何参数(如字符串,”hello”)都以正常方式传递给指定的方法。

可以说使用 send 获取私有方法的公共访问权通常不是一个好主意(否则,为什么你首先将该方法设为私有的),所以应谨慎使用或根本不使用…

单例类方法

之前,我们通过将方法名称附加到类的名称后面来创建类方法(class method),如下所示:

  1. def MyClass.classMethod

有一种“快捷”语法。这是个示例:

class_methods3.rb
  1. class MyClass
  2. def MyClass.methodA
  3. puts("a")
  4. end
  5. class << self
  6. def methodB
  7. puts("b")
  8. end
  9. def methodC
  10. puts("c")
  11. end
  12. end
  13. end

这里,methodAmethodBmethodC 都是 MyClass 的类方法;methodA 是使用我们之前使用的语法声明的:

def <ClassName>.<methodname>

但是,使用实例方法的语法声明了 methodBmethodC

def <methodname>

那么为什么它们最终成为类方法呢?这完全归结于方法声明已放在此代码中:

  1. class << self
  2. # some method declarations
  3. end

这可能会让你想起用于声明单例类(singleton classe)的语法。例如,在 singleton_class.rb 程序中,你可能还记得我们首先创建了一个名为 ob 的对象,然后给它声明了一个自己的方法,blather

  1. class << ob
  2. def blather( aStr )
  3. puts("blather, blather #{aStr}")
  4. end
  5. end

这里的 blather 方法是 ob 对象的单例方法(singleton method)。类似地,在 class_methods3.rb 程序中,methodBmethodC 方法是 self 的单例方法,而 self 恰好是 MyClass 类。我们可以类似地通过使用 << 后跟类名来从类定义之外添加单例方法,如下所示:

  1. class << MyClass
  2. def methodD
  3. puts( "d" )
  4. end
  5. end

嵌套方法

你可以嵌套(nest)方法(将一个方法嵌套在另一个方法中)。这为你提供了一种将长方法划分为可重用块的方式。因此,例如,如果方法 x 需要在几个不同的点进行 y 计算,则可以将 y 方法放在 x 方法中:

nested_methods.rb
  1. class X
  2. def x
  3. print( "x:" )
  4. def y
  5. print("ha! ")
  6. end
  7. def z
  8. print( "z:" )
  9. y
  10. end
  11. y
  12. z
  13. end
  14. end

嵌套方法默认在定义它们的作用域之外是不可见的。因此,在上面的示例中,虽然可以从 x 内部调用 yz,但是任何其它代码都不能调用它们:

  1. ob = X.new
  2. ob.y #<= error
  3. ob.z # <= error

但是,当你运行一个包含嵌套方法的方法时,这些嵌套方法将被带入该方法之外的作用域内!

nested_methods2.rb
  1. class X
  2. def x
  3. print( "x:" )
  4. def y
  5. print("y:")
  6. end
  7. def z
  8. print( "z:" )
  9. y
  10. end
  11. end
  12. end
  13. ob = X.new
  14. ob.x #=> x:
  15. puts
  16. ob.y #=> y:
  17. puts
  18. ob.z #=> z:y:

方法名称

最后一点,值得一提的是 Ruby 中的方法名称几乎总是以小写字符开头,如下所示:

  1. def fred

但是,这只是一个习惯约定,而非必须的。也可以用大写字母开头的方法名称,如下所示:

  1. def Fred

由于 Fred 方法看起来像一个常量(以大写字母开头),因此你需要在调用它时添加括号来告诉 Ruby,它是一个方法:

method_names.rb
  1. Fred # <= Ruby complains "uninitialized" constant
  2. Fred() # <= Ruby calls the Fred method

总的来说,最好坚持使用以小写字符开头的方法名称的约定。