Ruby
初心者
新人プログラマ応援
Effective_Ruby
Rubyist

Rubyのアクセス制御について簡潔に説明する~Public Protected Private~

概要

純粋なオブジェクト指向型言語であるRubyにおいて、
カプセル化という話は一つ重要なファクターになってきます。
今回はオブジェクト指向の抽象概念について解説する記事ではありませんので、
カプセル化についての説明は一般的に周知された表現で簡潔に説明するにとどめます。

 カプセル化…データや機能などをまとめて一つとして扱う(オブジェクト)概念。
Pythonなどでもオブジェクトはデータや機能を持ったものとして紹介されていますが、
Rubyでは特に、メソッドを介してのみオブジェクトのデータを取り出せる
という点が大事かな、と思います。

Rubyはそもそもオブジェクトがメソッドにのみ返答を返しますが、
更にアクセス制御によってメソッド自体を隠蔽することも可能です。

Java屋さんとかは得意な領域なのかなと思います(噂によるとRubyとJavaのアクセス制御はほぼ別モノのようですが…)

まずは軽く

  • Public
  • Protected
  • Private

について説明した後、
Effective Rubyレベルの話(主にProtectedについて)
をしようと思います。

Publicメソッド

もはやこれは言わずもがなですね。
例えばあるクラスがあった時に、

  • クラス内の別メソッドからも呼び出せる
  • クラス外からも呼び出せる
  • レシーバ付きでも呼び出せる

のがPublicメソッドです。
明示的にPublicメソッドを定義する事もできますが、
プレーンにクラス内に記述したメソッドは、
暗黙的にPublicメソッドに属します。

sample.rb
class Klass

    def self.foo
        puts "this is class method in Klass class"
    end

    def calling
        self.foo()
    end
end

ins = Klass.new()
Klass.foo()                   #=> this is class method in Klass class
ins.calling()                 #=> this is class method in Klass class

このコードにあるメソッドは全部Publicメソッドです。
レシーバを指定しても呼べるし、
別メソッドから呼び出してもエラーなどは出てませんよね。

殆どのメソッドはここに当てはまりますね。

Protectedメソッド

発展的な内容は後々記述するので、軽い仕様を説明。

まず、クラス外部から呼び出す事はできません。

sample.rb
class Klass
    def huga
        puts "huga method was called"
    end

    protected

    def foo
        puts "foo method was called"
    end
end

Klass.new.foo()      #=>Protected method 'foo' called for <オブジェクトID> NoMethodError

hugaメソッドについては言わずもがなPublicメソッドですね。
Protectedと書かれた行の下から、
その範囲に定義されたメソッドはprotectedメソッド扱いになります。

ワンライナーで記述する方法もあるようですがあんまり好きじゃないです。

因みにQiitaのRuby記事でよく見る

Class.new.method()

という形は一見混乱しますが、
インスタンスを生成した時に変数に代入しない場合です。
今回の様に一行だけインスタンス生成してメソッドを呼び出す時などには楽でいいですね。


次にクラス内部であればレシーバの有無を問わず呼び出せる例です。

sample.rb
class Klass
    def calling
        foo()                        
        Klass.new.foo()              
    end

    protected

    def foo
        puts "foo method was called"
    end
end

Klass.new.calling      #=> foo method was called (/n) foo method was called

普通に呼び出せてますね。

因みに、
メソッド内でインスタンスの生成が出来る、という点も自分は少しびっくりしました。

予めモデルとなるインスタンスを生成しておいて、
入力とインスタンスに相違があれば偽を返すクラスとか作れそうですね。


Privateメソッド

最後にPrivateメソッドですが、これは

  • クラス外からは勿論呼び出せない
  • クラス内でもレシーバ付きだと呼び出せない
  • 関数型メソッドライクな呼び出しは出来る

という仕様を持ってます。
Privateメソッドは有名ですからサクッと。

sample.rb
class Klass

    def initialize
        puts "initialize method was called"
        calling()
    end

    def calling
        foo()
    end

    private

    def foo
        puts "foo method was called"
    end

end

Klass.new  #=> initialize method was called (\n) foo method was called

foo()のように、レシーバを省略した関数型メソッドのような呼びかたならできますが、

ins.foo()とかself.foo()とかKlass.new::foo()
とかした途端参照できなくなります。

 Coffee break…
Rubyの記事を見ると、上記の様に
"Array::inject"
という記法見たことがあると思いますが、これは
Array#inject と同義です。Arrayクラスのinjectメソッド(インスタンスメソッド)ってことです。

この記法でメソッド呼び出しをゴリゴリ書くという事は少なくて、
基本的には定数を表記する際にこの記法を使う事が多いです。
私はこの記法かっこよくて気に入ってます。

また、この記法はクラスパスセパレータとかいい、
名前空間を利用したソースコードにおいては非常に重要な役割を担っているのですが…
それについては近い内に記事を作成して掲載したいと思います。
作成したらこちらの記事にリンク貼ります。

作成しました。
Rubyの名前空間とレキシカルスコープについて簡潔に説明する。

因みになんですけど

initializeメソッドも立派なPrivateメソッド

です。

そりゃそうですよね、インスタンス生成時にしたい処理を記述するinitializeメソッドを、
インスタンスごとに何回も呼び出せたら狂いますよ。

応用編、Protectedメソッドについて

ここからはEffective Rubyレベルです。

少しややこしくなってきますが、
用途が無いと言われているProtectedメソッドについての提案。

まず。上記で説明している以外の仕様を説明します。

sample.rb
class Parent
    def initialize
        puts "this is a initialize method from Parent class"
    end

    protected

    def foo
        puts "this is a protected method from parent class"
    end
end

class Child < Parent

    def calling
        foo()
    end
end

ins = Child.new
ins.calling

まず、スーパークラスで
Protectedに指定されたfoo()メソッドが定義されてます。

そして、サブクラスのcallingメソッドで親クラスのfoo()メソッドを呼び出しています。

雰囲気を掴む為に是非実行してみてください。

結果、

きちんとfooメソッドを呼び出すことができます。

Protectedメソッドは、継承階層をさかのぼって参照することが出来る。

RubyのパパたるMatz氏も、このことに付いて言及しているので調べてみて下さい。

例えば、
ある特定のクラスツリーに限定してメソッドの呼び出しを許可する
みたいな使い方ができますね。
英語圏クラス→アメリカクラス→ニューヨーククラス のツリー

日本語クラス→青森県クラス のツリー

みたいな風に作って

英語圏クラスにprotected CanSpeakEnglishメソッドを作れば、
英語圏クラスに属したアメリカクラスのインスタンスや
ニューヨーククラスのインスタンスはみんなCanSpeakEnglishを呼び出せます。

っていう感じですね。
プログラミング以外の例を示したほうがわかりやすいかなと思いました。

いやそれただの継承の説明やないか

いえいえ、隠蔽に一役買っているので多分大事なんだと思いますよ。

RubyのようなProtected制御が実際に役に立つのは、
多重継承しているような場合ですかね。

クラスツリーが二股に分かれるときとかに使うといい感じになりそう。

Rubyは単一継承ですからね…。

しかし、ここで悲しいお知らせ
通常、クラスを継承したサブクラスでは、

sample.rb
class Parent
    def foo
        puts "yeah,im god of foo"
    end
end

class Child < Parent

end

Child.new.foo()                    #=> yeah, im god of foo

と、サブクラス側に何も記述しなくても
親クラスのインスタンスメソッドを呼び出せますよね?
それがクラス継承なんだから当たり前と言われればそれまでですが。

ところがどっこい

sample.rb
class Parent

    protected

    def foo
        puts "yeah,im god of foo"
    end
end

class Child < Parent

end

Child.new.foo()                       #=>NoMethodError

こうなってしまうんですねぇ…

ココらへんはレキシカルスコープが影響してるのかな?という推測でいます。

レキシカルスコープという言葉は名前空間の記事で説明します。
まぁでもイメージはしやすいですよね。
サブクラス内でメソッド呼び出し=クラスの外部には出ていない=Protectメソッドが使える

っていう感じなんですかね。


自分はまだRubyで本格的に何か作ったことが一切ないので、
アクセス制御の恩恵については詳しくはわかりません。

でもいざ使うって時にこういうケースバイケースを知ってるか知らないかで

エラーを吐く回数を減らせるのかな?と思いました。