Ruby实例方法约束简谈

在Ruby的实例方法中分别有public
, private
以及protected
三种类型,不同类型的方法将会有不一样的访问约束。而这篇文章我主要想详细介绍一下他们之间的区别。
1. public方法
在类词法作用域中直接定义的实例方法都是公有方法。公有方法既可以在类词法作用域内部被其他实例方法调用,也可以在作用域外部创建实例并直接调用,简单举个例子
class Person
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def message
"Hello #{full_name}!!!"
end
def full_name
"#{@first_name} #{@last_name}"
end
end
@p = Person.new("zhiheng", "Lan")
puts @p.full_name # => zhiheng Lan
puts @p.message # => Hello zhiheng Lan!!!
上述定义的Person#message
与Person#full_name
都是公有的实例方法。
- 我们可以在实例化
Person
类之后,直接通过实例变量@p
来直接调用Person#full_name
方法。 - 当实例变量
@p
显式调用Person#message
的时候,该方法会在内部调用Person#full_name
方法。
2. private方法
许多时候我们并不希望把实例方法暴露到词法作用域之外,被创建的实例直接调用。这个时候就可以考虑把这些方法定义为私有方法。
与Java语言不同,在Ruby中我们并不需要在定义私有方法的时候在每个方法前面加上private
关键字,而只需要分配一块私有区域把所有的私有方法都写在这个区域里面。接下来我们把原有的Person#full_name
方法设置成私有方法,并添加一个名为Person#greet
的私有方法
class Person
def message
"#{greet} #{full_name}!!!"
end
private
def full_name
"#{@first_name} #{@last_name}"
end
def greet
"Hi,"
end
end
@p = Person.new("zhiheng", "Lan")
puts @p.greet
#=> NoMethodError: private method `greet' called for #<Person:0x00007f8598011dd0>
puts @p.full_name
# => NoMethodError: undefined method `full_message' for #<Person:0x00007f8598011dd0>
puts @p.message
# => Hi, zhiheng Lan!!!
PS: 对私有区域的代码进行缩进似乎是社区里面常有的做法。
可见,Person#greet
和Person#full_name
这两个方法我们无法再通过实例直接调用,因为他们已经被私有化了。但是在类词法作用域内部Person#full_name
依然可以被Person#message
这个实例方法调用。
3. protected方法
保护方法与私有方法之前的区别比较微妙。他们之间的相同点都是不能在类词法作用域之外被实例直接调用,但比起保护方法它的约束更多
在类词法作用域内,我们无法给予私有方法一个确切的接收者。
举个例子,分别定义一个私有方法以及一个保护方法,并在类词法作用域内部通过另外一个实例方法来调用他们
class Person
def call_method
puts protected_method
puts private_method
puts self.protected_method
puts self.private_method # will raise error
end
private
def private_method
"I am private method"
end
protected
def protected_method
"I am protected method"
end
end
@p = Person.new("zhiheng", "Lan")
@p.call_method
# => I am protected method
# => I am private method
# => I am protected method
# => NoMethodError: private method `private_method' called for #<Person:0x00007f8598011dd0>
可见,如果给私有方法添加一个显式的接收者self
的话,它就会报错了,而保护方法在这种情况下却可以正常运行,这就是他们之间最大的不同。
再举个例子,添加一个方法Person#==
用来判断两个Person
实例是否是同一个人(只要Person#full_name
相同则认为是同一个人)。
class Person
def ==(other)
self.full_name == other.full_name
end
end
@lan = Person.new("zhiheng", "Lan")
@liang = Person.new("haidao", "Liang")
puts @lan == @liang
# => NoMethodError: private method `full_name' called for #<Person:0x00007f85990f91c0>
因为之前的Person#full_name
方法被定义成私有的了,所以这个时候显式为它指定接收者self
的话就会抛出异常。解决方案就是把它重新定义为一个保护方法
class Person
def ==(other)
self.full_name == other.full_name
end
protected
def full_name
"#{@first_name} #{@last_name}"
end
end
@lan = Person.new("zhiheng", "Lan")
@liang = Person.new("haidao", "Liang")
puts @lan == @liang
# => false
比起公有以及私有方法,保护方法的应用范围似乎并不是那么多,但理解他们之间的区别对于我们这些Ruby程序员来说还是很有必要的。
4. 继承关系中的约束现象
在Ruby中,三种类型的实例方法都能够在子类的词法作用域中被直接调用,其行为就像是在子类中也定义了相关的方法。举个简单的例子
class Animal
def initialize(name, age)
@age = age
@name = name
end
def name
@name
end
private
def greet
"hello world"
end
protected
def age
@age
end
end
class Cat < Animal
def test
puts name
puts greet
puts age
end
end
Cat.new("tom", 1).test
# tom
# hello world
# 1
可见,即便是私有的方法Animal#greet
,在类被继承后却依然能够在子类的词法作用域中直接调用。接下来我再往Cat
类中定义两个实例方法,看些稍微有趣的现象
...
class Cat
def private_or_protected
puts greet #4
puts Animal.new('James', 24).age #2
puts Animal.new('James', 24).greet #3
end
private
def greet #5
"hello cat"
end
end
puts Animal.new('James', 24).age #1
# NoMethodError: protected method `age' called
Cat.new("tom", 1).private_or_protected
# hello cat
# 24
# NoMethodError: private method `greet' called
对比#1
与#2
,Animal#age
作为一个保护方法,虽然无法在顶级作用域中通过实例直接调用,但如果我们是在子类Cat
的词法作用域中对Animal
进行实例化,便可以直接调用Animal#age
方法了。
再对比#2
与#3
,私有方法Animal#greet
无法像保护方法Animal#age
那样在子类的词法作用域中被父类的实例直接调用,毕竟我们不能够给一个私有方法添加显式的接收者。
代码片段#5
可以对父类的Animal#greet
这个私有方法进行重写,在片段#4
中直接调用。
5. Ruby的私有方法真的有那么私有吗?
Ruby给予了程序员最大的自由度,公私有方法在Ruby中与其说是约束,还不如说是分类。在词法作用域外部,对象通过Object#send
方法依然可以直接调用私有方法,例子如下
class Say
private
def hello
"Hello World"
end
end
@s = Say.new
@s.hello
# => NoMethodError: private method `hello' called for #<Say:0x007fc44e929130>
@s.send(:hello)
# => "Hello World
用这种黑科技来调试方法还是很方便的。
你能够做任何事情,但你要你清楚自己在做什么。
6. 尾声
本章主要展示在Ruby里面private
, public
, protected
这三种类型的实例方法之间的区别。后面又谈到Ruby里面的私有方法其实也没那么私有,在必要时我们依然可以通过一些黑科技来直接调用它们。
参考文献
-
The difference between Public, Protected and Private methods in Ruby
-
Are there good reasons for ‘private’ to work the way it does in Ruby?