存取限制 <> 原來是海樓石!惡魔果實能力者的剋星

https://ithelp.ithome.com.tw/upload/images/20200923/20128363iznYQbH4HN.jpg

[Day15] 再厲害的能力者一碰到海樓石就會喪失戰鬥力!

今天要和大家介紹 Ruby 裡的存取控制(Access control)

先來看看維基百科怎麼說:

存取控制 是指允許或禁止某人使用某項資源的能力。

聽起來是不是超強!

不過在 Ruby 裡,存取控制其實就是方法的使用層級。當我們在類別裡定義方法時,還可以再更明確指出這個方法是只有誰才可以取用,共分為三種:

  • public
  • private
  • protected

Public

如果沒有特別寫是哪種存取方式,就會預設用 public 來存取我們定義的方法。

在撰寫存取控制的層級時,與方法定義前綴詞同一行即可:

1
2
3
4
5
6
class Cafe
public # 通常直接省略不寫
def coffee
puts "來杯西西里拿坡里左岸八里漂浮拿鐵!"
end
end

換句話說,平常不寫 public 也沒關係,除非你在定義了 private 方法後又要定義 public 方法才要特別加上去,不過通常我們是不會這樣做的,後面會提到原因。


Private

private 則是指所定義的方法不能有明確的訊息接收者(receiver),意思就是不能用.來呼叫方法

像是 puts 就是一種很常見的 private 方法,不過我們自己定義一個 private 方法 seasoning 試試看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cafe
def coffee
puts "來杯西西里拿坡里左岸八里漂浮拿鐵!"
end

private
def seansoning
puts "還要有絕妙的香氣!"
end
end

my_store = Cafe.new

my_store.coffee
my_store.seasoning

# 印出
來杯西西里拿坡里左岸八里漂浮拿鐵!
NoMethodError (undefined method `seasoning' for #<Cafe:0x00007f903c945538>)

我開的咖啡廳裡面沒有絕妙的香氣!


private 寫的方法要用在哪裡?

看來 private 的方法沒辦法像之前的方式直接呼叫,那它到底是用在哪裡呢?事實上,Ruby 裡用 private 寫的方法是拿來在類別內部呼叫的,像是這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Cafe
def coffee
puts "來杯西西里拿坡里左岸八里漂浮拿鐵!"
seasoning
end

private
def seasoning
puts "還要有絕妙的香氣!"
end
end

my_store = Cafe.new

my_store.coffee

# 印出
來杯西西里拿坡里左岸八里漂浮拿鐵!
還要有絕妙的香氣!

private 方法在子類別也可以存取

在 Ruby 裡,private 寫的方法不只在類別內部可以存取,繼承自這個類別的子類別也一樣可以!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Father
private
def heritage(gift)
puts "#{gift} 是我留給兒子的財產!"
end
end

class Son < Father
def initialize(item)
heritage(item)
puts "謝謝老爸!"
end
end

Son.new("PS5")

# 印出
PS5 是我留給兒子的財產!
謝謝老爸!

我也想要有個這樣的老爸(羨慕)


來看看 private 方法有哪些!

可以用 self.class.private_methods.sort 來找 :

1
2
self.class.private_methods.sort
=> [:Array, :Complex, :DelegateClass, :Float, :Hash, :Integer, :Rational, :String, :URI, :__callee__, :__dir__, :__method__, :`, :abort, :at_exit, :binding, :block_given?, :caller, :caller_locations, :catch, :eval, :exec, :exit, :exit!, :extended, :fail, :fork, :format, :gem, :gem_original_require, :gets, :global_variables, :included, :inherited, :initialize, :initialize_clone, :initialize_copy, :initialize_dup, :irb_binding, :iterator?, :lambda, :load, :local_variables, :loop, :method_added, :method_missing, :method_removed, :method_undefined, :open, :p, :prepended, :print, :printf, :private, :proc, :protected, :public, :putc, :puts, :raise, :rand, :readline, :readlines, :remove_const, :require, :require_relative, :respond_to_missing?, :select, :set_trace_func, :singleton_method_added, :singleton_method_removed, :singleton_method_undefined, :sleep, :spawn, :sprintf, :srand, :syscall, :system, :test, :throw, :trace_var, :trap, :untrace_var, :using, :warn]

發現了嗎? privatepublic都是 private 方法!


其實也不是這麼 private

那… private 裡面的方法是不是就再也無法拿出來用呢?其實也未必。我們可以用 .send() 來取出 private 方法,只是這樣會破壞封裝的原則,非不得已建議不要這樣做。

1
2
3
4
5
6
7
8
9
10
11
12
class Man
private
def whisper
puts "這是只有 private 才聽得到的悄悄話..."
end
end

man = Man.new
man.send(:whisper)

# 印出
這是只有 private 才聽得到的悄悄話...

果然成功拿出來了!


Protected

這是一個既不 public 又不 private 的東西。

protectedprivate 最大的不同,是 protected 可以允許在呼叫時前面有 receiver,這種承繼自 SmallTalk 的設計理念是 Ruby 的一大特點,不過可能之後才會詳細介紹這段。

但由於實際上現在幾乎沒有人在用 protected 寫 Ruby 的存取控制,就讓它慢慢地成為時代的眼淚吧!

1
2
3
4
5
6
7
8
9
10
11
class Smalltalk

def say_hello_to_myself
self.hello
end

protected
def hello
puts "murmur..."
end
end

如果是用 protected,那麼在內部呼叫時要寫 self.hellohello 都可以,但改成寫 privateself.hello 就會出錯了。


存取控制的另一種寫法

也可以這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Gem
def ruby
end

def emerald
end

def diamond
end

protected :emerald
private :diamond
end

這麽一來,emerald 就變成了 protected 方法,而 diamond 成為了 private 方法


總結

publicprotectedprivate 的特點有三:

  1. 類別內部都可以呼叫 3 種類型的方法。
  2. 只有 public 方法能在外部被呼叫。
  3. 正常情況下 private 的方法不允許有 receiver, protected 則沒有這種限制。不過用 send() 可以取消 private 原本的限制。

在實際撰寫時,我們會把 private 的方法定義在類別的最下方,這樣就能知道 private 上方的通通都是 public 的方法,也能更輕鬆地閱讀程式碼。


關於 Ruby 方法的存取控制,今天就介紹到這邊啦!

參考來源:
高見龍 - Public, Protected and Private Method in Ruby