ume

Ruby 呼び出し制限(public,private,protected)について

前書き

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)

このように業務系のプログラムで特に大切な部分(データベースにデータ登録することやお金の計算の処理など)が安易に色々なとこで使用できないように縛りを課している