🤨privateとprivate

created_at:
updated_at:

目次

privateが2つ定義されていたのだが…

僕は普段業務ではRubyを扱っていて、最近privateが同一ファイル上に2つあることに気づいた。

💡 privateメソッドはクラスの内部からしか呼び出せないメソッド(レシーバがselfに限定されるメソッド)

他のファイルでは、privateの定義は一つしかない。

誰かがファイルの下にあるprivateを間違えて定義してしまったものを放置しているのではないか?と考え安易に削除してPRを出したのだが、これが誤りだった。

指摘をもらったことで救われたので忘れないうちに復習。

消してはいけなかった理由

よく目にするものは純粋なprivateメソッド。

privateの下に記述されたメソッドが全てprivateメソッドとなるというやつだ。

それしかないものだと思っていたから今回のようなことになってしまったわけだが、完全に見落としていたのだ。privateなクラスメソッドのことを。

以下が1000行を超えるファイル内に存在して「これミスってるのでは?」と思ってしまった…

こうしてみると明らかに違う。

class Apple

 # クラスメソッド
  class << self
    def slice
      '剥く'
    end
    .
    .
    .
    
    private
    
    # クラスメソッドのprivateメソッド(ややこしい...)
    def eat
      '食べる'
    end
    
  end

  .
  .
  .
  .
  # これより下に記述されたものはAppleクラス内部からしか呼び出せない。つまりApple.methodの形を取る必要がある
  # これがよく目にする形式。この書き方でprivateメソッドになるのはインスタンスメソッドのみ
  # クラスメソッドはこの下に記述してもprivateにはならないので上のような書き方をする必要がある。
  private

  def cut
    '切ります'
  end
  
end

実際に呼び出してみる

apple = Apple.new
apple.cut
# NomethodErrorとなり外からは呼べない。

Apple.cut
# NomethodErrorとなり外からは呼べない。

# クラスメソッドは呼べる
Apple.slice
#=> "剥く"

# クラスメソッドのprivateメソッドは呼べない
Apple.eat
#=> NomethodError

ついでにprivateメソッドについて新しく学んだこと

せっかくなので少し踏み込んで学習してみることにする。

こういう小さな積み重ね。最初はめんどいなとは思うけどやりだすとと歯止めが効かなくなる。

Ruby2.6系と2.7系以降ではprivateメソッドの呼び出しに変更がある

Ruby2.6までは「privateメソッドは明示的にレシーバを指定できない」というルールがあったので、クラスの内部であってもselfつき呼び出しをすることができなかった。

しかし、Ruby2.7からはselfをつけてprivateメソッドを呼び出すことが許可された。

class User
  def hello
    "Hello, I am #{self.name}"
  end

  private

  def name
    'Alice'
  end
end

Ruby2.6以前

user = User.new
user.hello
#=> NomethodError

Ruby2.7以降

user.hello
#=> "Hello, I am Alice"

privateメソッドはサブクラスでも呼び出せる

他の言語では、「privateメソッドはそのクラスの内部でのみ呼び出せる」という仕様になっているらしいが、Rubyの場合は「privateメソッドはそのクラスのみならず、サブクラスでも呼び出せる」という仕様になっている。

class Product
  private
  
  def name
    'A great movie'
  end
end

class DVD < Product
  def to_s
    # nameはスーパークラスのprivateメソッド
    "name: #{name}"
  end
end

dvd = DVD.new
# 内部でスーパークラスのprivateメソッドを呼んでいるがエラーにならない。
dvd.to_s
#=> "name: A great movie"

継承できるということはオーバーライドも可能ということ。

class Product
  def to_s
    # nameは常に"A great movie"となるとは限らない
    "name: #{name}"
  end

  private
  
  def name
    'A great movie'
  end
end

class DVD < Product
  private
  
  def name
    'An awesome film'
  end
end

product = Product.new
# Productクラスのnameメソッドが使われる
product.to_s
#=> "name: A great movie"

dvd = DVD.new
# オーバーライドしたDVDクラスのnameメソッドが使われる
dvd.to_s
#=> "name: An awesome film"

サブクラスのサブクラス(サブサブクラスとでもするか。)から大元の親クラスのprivateメソッドも継承できるか試してみたのだが、呼び出すことができた。ずっと継承されるみたい。

Rubyでは継承を使う場合はスーパークラスの実装もしっかりと把握する必要があるということだ。意図しない変更とならないよう注意を払いたい。

さいごに

privateメソッドはクラスメソッドの中に定義されることも全然ある。ということを改めて学んだ。

privateメソッドを自分自身が実装したことはまだないが、いざやるとなった際にすぐ動ける状態になっておきたい。何事においても。

Buy Me A Coffee