iT邦幫忙

2021 iThome 鐵人賽

DAY 10
0
自我挑戰組

Ruby on Rails JS系列 第 10

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


上一篇
Ruby on Rails 實體方法與類別方法
下一篇
Ruby on Rails 繼承(Inheritance)與開放類別
系列文
Ruby on Rails JS29

尚未有邦友留言

立即登入留言