前書き
protectとprivateの違いを理解が曖昧だったので理解を深めるために記事に残します。
結論
そもそも呼び出し制限とは?
⇨どこからでもメソッドを呼び出せないようにすること。
呼び出しを制限する方法3種類(public,private,protected)
公開レベル | クラスの外部から直接呼べるか | 自クラス及びサブクラスから 関数形式での呼び出し |
自クラス及びサブクラスから レシーバ形式での呼び出し |
---|---|---|---|
public | ○ | ○ | ○ |
private | × | ○ | × |
protected | × | ○ | ○ |
クラスの外部から直接呼べるか検証(public,private,protected).
これからクラスの外部とクラスの内部という言葉を使いますが以下のことを指しています.
クラスの内部.
⇨class Car~end の中のこと.
クラスの外部を表しています.
⇨class Car~endの外のこと.
図にするとこんな感じ↓
検証1 public指定されたメソッド(acceleとbrake)をクラスの外部から呼んでみる
例
class Car def accele ⇦public puts "車は進む" end def brake ⇦public puts "車は止まる" end end car = Car.new car.accele #出力結果 車は進む car.brake #出力結果 車は止まる
クラスの外部から直接(car.acceleやcar.brake)指定できて実行できているので上記の表の通り○になります.
とここでの僕の疑問
「メソッドをpublicになんて指定してないぞ?」です.
⇨公式ドキュメントによるとクラス内の全てのメソッドはデフォルトでpublicに設定されているみたいです。ただしinitializeメソッドだけはどこに記述してもprivateの設定になっているみたいです
クラス定義の中にあれば public に定義します。ただし Object#initialize という名前のメソッドと Object#initialize_copy という名前のメソッドは定義する場所に関係なく常に private になります。
検証2 private指定されたメソッド(acceleとbrake)をクラスの外部から呼んでみる
class Car private ⇦と記載するとここより下はprivate指定のメソッドになる def accele puts "車は進む" end def brake puts "車は止まる" end end car = Car.new car.accele 出力結果`<main>': private method `accele' called for #<Car:0x000000013e199af8> (NoMethodError) car.brake 出力結果`<main>': private method `brake' called for #<Car:0x000000013e199af8> (NoMethodError)
クラスの外部から直接privateで指定したメソッドは呼べないことがわかりました。
検証3 protected指定されたメソッド(acceleとbrake)をクラスの外部から呼んでみる
class Car protected ⇦と記載するとここより下はprotected指定のメソッドになる def accele puts "車は進む" end def brake puts "車は止まる" end end car = Car.new car.accele 出力結果`<main>': protected method `accele' called for #<Car:0x000000013e199af8> (NoMethodError) car.brake 出力結果`<main>': protected method `brake' called for #<Car:0x000000013e199af8> (NoMethodError)
protectedはprivateと同じ書き方で同じエラーが出力されメソッドを呼び出せないことがわかる。
自クラス及びサブクラスから関数形式での呼び出し
用語解説.
メソッドの呼び出し方3つ
①メソッド名のみ(関数形式) ②インスタンス.メソッド(インスタンスメソッド、レシーバ形式). ③クラス名(先頭大文字).メソッド(クラスメソッド)
今回はメソッド名のみで実行した場合のpublic,private,protectedの挙動の違いを検証していきます。 要は
class Car def accele puts "車は進む" brake (メソッド名のみでメソッドを実行した場合の挙動の違いです) end def brake ⇦ここのbrakeメソッドをacceleのメソッドの中で実行しています puts "車は止まる" end end
検証1 public指定でメソッドを実行してみます。
class Car def accele puts "車は進む" brake end def brake puts "車は止まる" end car = Car.new car.accele 出力結果 車は進む 車は止まる
ちゃんと2つのメソッド(acceleがbrake)が実行されていることがわか理ました。
検証2 private指定でメソッドを実行してみます。
class Car def accele puts "車は進む" brake end private def brake puts "車は止まる" end car = Car.new car.accele 出力結果 車は進む 車は止まる
brakeメソッドをprivateにしてacceleメソッドの中で実行してもメソッドが呼べました。
検証3 protected指定でメソッドを実行してみます。
class Car def accele puts "車は進む" brake end protected def brake puts "車は止まる" end car = Car.new car.accele 出力結果 車は進む 車は止まる
同じようにbrakeメソッドをprotectedにしてacceleメソッドの中で実行してもメソッド(brake)が呼べました。
サブクラスも3つとも同様の結果(呼び出せる)ので割愛します
自クラス及びサブクラスからレシーバ形式での呼び出し
検証1 public指定でメソッド(say_owner)を実行してみます。
class Car def accele puts "車は進む" brake end def brake puts "車は止まる" end def say_owner(name,car_type) puts "これは#{name}さんの#{car_type}です" end end class SportsCar < Car def output(name,car_type) sports_car = SportsCar.new sports_car.say_owner(name,car_type) end end sports_car = SportsCar.new sports_car.say_owner("鈴木","ポルシェ") 出力結果 これは鈴木さんのポルシェです
publicだとサブクラスのインスタンスメソッドでも呼び出せます。
検証2 private指定のメソッドをサブクラスで呼んでみる
class Car def accele puts "車は進む" brake end def brake puts "車は止まる" end private def say_owner(name,car_type) puts "これは#{name}さんの#{car_type}です" end end class SportsCar < Car def output(name,car_type) sports_car = SportsCar.new sports_car.say_owner(name,car_type) end end sports_car = SportsCar.new sports_car.output("鈴木","ポルシェ")出力結果 output': private method `say_owner' called for #<SportsCar:0x0000000124178d18> (NoMethodError)
privateで指定したメソッドはインスタンスメソッドの形では呼び出せない。
検証3 protected指定のメソッドをサブクラスで呼んでみる
class Car def accele puts "車は進む" brake end def brake puts "車は止まる" end protected def say_owner(name,car_type) puts "これは#{name}さんの#{car_type}です" end end class SportsCar < Car def output(name,car_type) sports_car = SportsCar.new sports_car.say_owner(name,car_type) end end sports_car = SportsCar.new sports_car.output("鈴木","ポルシェ")出力結果これは鈴木さんのポルシェです
そもそもなんで呼び出し呼び出し方を制限するの?
⇨クラス内の他のメソッドから実行されるだけのメソッドが、クラス外から実行できるようになっていると予想外の結果になる可能性がある。
class BankAccount attr_accessor :balance def initialize(balance) @balance = balance end # 不用意な操作をするためにpublicメソッドを定義 public # 不用意な引き出しを可能にするpublicメソッド def withdraw(amount) @balance -= amount puts "#{amount} withdrawn. Remaining balance: #{@balance}" end end # BankAccountインスタンスを作成 account = BankAccount.new(1000) # 不用意な引き出しを試みる(外部からアクセス可能) account.withdraw(500)
このように業務系のプログラムで特に大切な部分(データベースにデータ登録することやお金の計算の処理など)が安易に色々なとこで使用できないように縛りを課している