Ruby on Rails 方法的存取控制

如果你曾经在别的程序语言写过OOP,你也许对类别的方法存取限制不会太陌生。类别的方法存取限制常见的主要有三种:public、protected 以及 private。

这三种存取限制,在别的程序语言常听到的解释大概会是像这样:

public: 就是所有的人都可以直接存取。
private: 是只有在类别内部才可以存取。
protected: 差不多是在这两者之间,比 private 宽松一些,但又没有 public 那麽自在,protected 在同一个类别内或是同一个 package,或是继承它的子类别可以自由取用,但如果不是的话则不可存取。
但 Ruby 的设定在这方面跟其它程序语言不太一样,不过让我们先来看看怎麽使用。Ruby 的方法存取限制有两种写法,一种是写在方法定义之前:

class Cat
  def eat
    puts "好吃!"
  end

  protected
  def sleeping
    puts "zzzzzzzzz..."
  end

  private
  def gossip
    puts "我跟你说,你不要跟别人说喔!"
  end
end

这里定义了三个方法,分别是 public 的 eat 方法、protected 的 sleeping 方法,以及 private 的 gossip 方法。在 Ruby 的类别里,方法如果没有特别限制,预设就是 publilc,也就所有的类别都可以存取它。

这种把存取控制放在前面的写法,在它设定之後的方法都会受影响,除非又遇到另一个存取控制的设定。以上面这个例子来说,eat 方法没有特别限制,所以它是 public 方法(如果你想要特别加上 public 也可以,只是通常不会这麽做)。

Ruby 冷知识:
没有特别限制的方法预设都是 public,但除了一个例外,就是负责初始化的 initialize 方法,它永远是 private 的,只会被 new 方法呼叫。

另一种的方法存取限制是写在方法定义之後:

class Cat
  def eat
    puts "好吃!"
  end

  def sleeping
    puts "zzzzzzzzz..."
  end

  def gossip
    puts "我跟你说,你不要跟别人说喔!"
  end

  protected :sleeping
  private :gossip
end

这两种哪种方法比较好呢?都好,随个人喜好。我个人喜好第一种,因为我习惯会先把 public 的方法放在类别的上半部,把 private 方法放在类别的最底下,所以使用第一种写法对我来说写起来比较顺手。

Ruby 冷知识:
其实 public、protected 以及 private 这三个在 Ruby 里并不是关键字,它只是一般的方法而已。

前面为什麽会特别提到 Ruby 的方法存取限制跟其它的程序语言「类似」呢?虽然 Ruby 里的确也有 public、protected 以及 private ,但事实上是不太一样的,特别是 private 方法。我们先来看一小段的程序码:

kitty = Cat.new
kitty.eat           # => "好吃!"
kitty.sleeping      # => NoMethodError
kitty.gossip        # => NoMethodError

kitty 是 Cat 类别产出的实体,而实体的 public 方法如所预期的印出结果,protected 跟 private 方法呼叫的时候产生 NoMethodError 例外,到这里看起来都还跟其它程序语言的设计差不多。

再继续往下说明之前,先让我们看一下这段程序码:

kitty.eat
这看起来非常普通,不就是「kitty 物件执行了 eat 方法」而已吗?其实这个在 Ruby 里,它被解读成:

有一个 kitty 物件,对它发送了一个 eat 的「讯息」(message),而这个 kitty 就是讯息的「接收者」(receiver)。

这个概念是来自於一个非常古老的程序语言 Smalltalk。为什麽特别提这个?因为在 Ruby 里所谓的 private 方法的使用规定很简单,就只有一条:「不能明确的指出 receiver」。用白话文讲,就是「在呼叫 private 方法的时候,前面不可以有小数点」。也就是因为这样,在 Ruby 的 private 方法其实不只类别自己内部可以存取,它的子类别也可以,并没有像其它程序语言一样的继承限制。

再让我们看这个例子:

class Cat
  def say_hello
    self.gossip
  end

  private
  def gossip
    puts "我跟你说,你不要跟别人说喔!"
  end
end

kitty = Cat.new
kitty.say_hello  # NoMethodError

也许你有听过 self 这个特别的变数,指的是「自己」,上面这个例子,say_hello 方法执行会发生错误。为什麽?因为你在呼叫 private 方法的时候加上了 self。就跟你说「在呼叫 Ruby 的 private 方法时,不能明确的指定 receiver」,不管你是不是 self,违反规定就无法使用 private 方法。

也许你会好奇,private 方法常见吗?其实,我们很常用的 puts 方法,它其实就是 Object 这个类别的 private 方法之一(更正确的说,是 Kernel 模组 mixin 到 Object 类别里的方法)。我们平常会这样用:

puts "Hello Ruby"
你有注意到呼叫 puts 方法的时候,前面没有小数点吗?但如果你这样做:

self.puts "Hello Ruby"
就会出现出 NoMethodError 了。

那 protected 方法呢?从外部来看,它跟 private 一样,不能直接使用,但在类别内部,它的规定就没那麽严格了,你要指定或不指定 receiver 都可以;至於public方法,就跟其它语言的定义差不多,就是随便你用啦。

真的这麽 private?
不过,其实 Ruby 的 private 方法也不是真的那麽 private,转个弯,一样可以被外部呼叫:

kitty = Cat.new
kitty.gossip          # => NoMethodError
kitty.send(:gossip)   # => 我跟你说,你不要跟别人说喔!

咦?不是说呼叫 private 方法的时候不能有明确的接收者吗?你仔细看,并没有违反这个规定喔,这边我是执行 send 方法,把 gossip 当做参数传给它而已,所以不算违反规定。

仅供参考
这..这样会不会太随便了?如果连 private 方法都能被直接存取,那当初何必还要这样设计呢?还是直接乾脆全部都 public 就好了?

我想这其实是 Ruby 当初设计的哲学之一,Ruby 把很大部份的权限都下放给程序设计师,让开发者有最大的弹性空间可以运用(或恶搞),也就是这样,跟别的程序语言比起来,在 Ruby 做 Metaprogramming 是相对的较容易的。不只在这里,你应该还可以在很多地方看到这个 Ruby 的有趣的特性。

[为你自己学Ruby on Rails]https://railsbook.tw/chapters/08-ruby-basic-4.html


<<:  Flutter体验 Day 2-环境设定

>>:  Day 10 : 用於生产的机械学习 - Data Define 与建立基准

I Want To Know React - 中场休息

铁人炼成,回顾三十天 三十天过去了,没想到我竟然成功完成铁人赛了! 上次铁人赛完赛心得的第一句话是 ...

Day 10. 状况篇 Zabbix 安装问题排除

今天跟大家分享从维运手册调出来的遇到问题与排除。 就是如果在 WEB 介面设定完以後发现设定失败该怎...

Day 25:专案06 - 股市趋势图02 | 整年股市资料、Postman

复习一下昨天的进度 - 我们取得单月的个股日成交价的资料,并在电脑中储存成csv档。 目前都只有单月...

[第九只羊] 迷雾森林舞会II 房间座位设定

天亮了 昨晚是平安夜 关於迷雾森林故事 粉红烟花三个月 由於黑洞把12只 animal 吸走後的烟 ...

电子书阅读器上的浏览器 [Day07] 改善更多的 UI

在 Day02 时有提到,电子纸萤幕设备上的 UI 设计原则是减少画面的重绘。我们可以看到上面图中...