Rubyのオブジェクト指向におけるクラスとモジュール、継承、Mixin、アクセス制御の使い方若手エンジニア/初心者のためのRuby 2.1入門(7)(4/5 ページ)

» 2014年08月28日 18時00分 公開
[著:麻田優真、監修:山根剛司株式会社アジャイルウェア]

オブジェクトの「アクセス制御」

 C#やJavaなどのオブジェクト指向型言語を使った経験のある方は、「private」「public」といった、メソッドやインスタンス変数の可視性を制御するキーワードについてご存じでしょう。Rubyでは、クラス定義の中で定義したメソッドは、基本的にpublic、つまりオブジェクトの外から利用可能です。

 ただし、クラス内で使うだけで、オブジェクトの外からアクセスしてほしくないメソッドを書きたい場合もあると思います。公開すべきメソッドだけを外部に公開し、それ以外は隠しておくのが基本です。

Rubyには3種類の可視性がある

 Rubyには3種類の可視性があります。以下にその一覧を示します。

  • public:クラス定義の中でも外からでも、無制限に呼び出せる
  • protected:「protected」が設定されたメソッドは、そのメソッドを持つオブジェクトが「self」であるコンテキスト内でのみ呼び出せる
  • private:関数形式でしか呼び出せないメソッドで、現在のオブジェクトのコンテキスト内でのみ呼び出せる。オブジェクトの外部から呼び出せない。「レシーバー」は暗黙的に「self」となる

 ここで、コンテキストやレシーバー、selfなどの聞きなれない言葉がいくつか出てきました。今回の目標は、クラスやモジュールについて大まかな理解をすることが目的ですので、アクセス制御についての込み入った話はしません。ですので、ここでは可視性の設定方法を説明するにとどめます。次回、メソッドについて掘り下げて解説するときに、あらためて可視性について触れたいと思います。

アクセス制御メソッドの使い方

 accessibility01.rbにアクセス制御メソッドの使い方を示します。

class A
  def a; end
  def b; end
 
  private
  def c; end
  def d; end
 
  protected
  def e; end
end
accessibility01.rb

 このように、privateやprotected、publicといったアクセス制御メソッドを呼び出すと、それ以降に定義する全てのメソッドにはその可視性が適用されます。この場合、メソッドaとメソッドbはpublic(デフォルトではpublicであることを思い出してください)、メソッドcとメソッドdはprivate、メソッドeにはprotectedな可視性が設定されます。

可視性をシンボルを使って設定

 また、可視性をシンボルを使って設定することもできます。

class A
  def a; end
  def b; end
  def c; end
  def d; end
  def e; end
 
  public :b
  private :c, :d
  protected :e
end
accessibility02.rb

 この場合も、メソッドaとメソッドbはpublic、メソッドcとメソッドdはprivate、メソッドeはprotectedな可視性を持つようになります。

補足「メソッドの可視性は破れる!?」

 privateに設定したメソッドは外部から呼び出せないと説明しましたが、実はRubyではその原則を簡単に破ることができます。pryを起動して、以下のように入力してみてください。

[1] pry(main)> class A
[1] pry(main)*   private
[1] pry(main)*   def private_method
[1] pry(main)*     puts "PRIVATE!"
[1] pry(main)*   end  
[1] pry(main)* end  
=> :private_method
[2] pry(main)> A.new.private_method
NoMethodError: private method `private_method' called for #<A:0x007f85051789a0>
from (pry):7:in `__pry__'
[3] pry(main)> A.new.send(:private_method)
PRIVATE!
=> nil

 [1]ではクラスAを定義し、その中でprivateなメソッドprivate_methodを定義しています。このメソッドはprivateに設定されているので、[2]では「そのようなメソッドはないよ」というエラーが出ています。注目すべきは[3]の結果です。「Object#send」メソッドにメソッド名を渡すと、いとも簡単に可視性を無視して「A#private_method」メソッドを呼ぶことに成功しました。

 C#やJavaなどの一般的なオブジェクト指向型言語では、基本的に可視性を簡単に破ることはできません。できたとしてもリフレクションを使うなど、簡単にはできないようになっています。

 Rubyでは簡単に可視性を破れる以上、privateなどメソッドを使った可視性の制御は、クラスの設計者が「これはprivateにしたいんだよ」という意思表示程度の力しか持たないと思った方がいいでしょう。

 「send」メソッドをはじめとするメタプログラミングでよく使われるメソッドは、乱用すると危険な仕組みです。しかし、それ故にプログラマーに強力な抽象化の仕組みを与えています。

 これはRubyの思想の一側面で、Rubyはプログラマーを信用して全能の力を与えます。一方で、C#やJavaといった言語は「プログラマーはいつでも間違いを犯すものだ」と仮定して、言語がプログラマーの力を抑制しています。

 どちらの方が優れている考え方なのかという議論は、プログラマーの間でよく酒の肴になります。一長一短があるので、結局「適材適所」ということになるのですが、筆者としては全能の力を与えてくれるRubyの方が好きです。C#も良い言語ですが、Rubyに比べると、書いてて砂を噛んでいるような気分になってしまうのです。

 この辺りの話は、以降の連載のメタプログラミングの回でもう一度触れることにします。


モジュールとMixin

 ここまで、長々とクラスについて解説しました。RubyのクラスではC++やC#、Javaなどのオブジェクト指向型の言語と同様、クラスを定義して継承できます。ただし、C++と違ってRubyではクラスの「多重継承」を許しません。その代わりに、Rubyでは「モジュール」と「Mixin」という強力な仕組みによってコードを分割できます。

 厳密には、「クラスはモジュールの一種であり、クラスはモジュールである」といえます。ただし、クラスの情報を基にオブジェクトを生成できますが、クラスではないモジュールにはオブジェクトを生成する力はありません。

 ここでは基本的に、単に「モジュール」という場合はクラスでないモジュールを指すものとします。

 ここからは、モジュールの定義の仕方とその利用法について解説した後、クラスとの使い分けについて触れたいと思います。

モジュールを定義してMixinしてみる

 ここからは、ウサギを表すクラス「Rabbit」、アヒルを表すクラス「Duck」と、ハムスターを表すクラス「Hamster」を例に挙げ、モジュールについて理解を深めていきます。

 例えば、ウサギもアヒルもハムスターも、「歩く」という点で共通しています。そこで、「歩けるもの」ということを表す「Walkable」というモジュールを定義して、Mixinしてみましょう。

module Walkable
  def walk
    puts "てちてち"
  end
end
 
class Rabbit
  include Walkable
end
 
class Duck
  include Walkable
end
 
class Hamster
  include Walkable
end
 
Rabbit.new.walk
Duck.new.walk
Hamster.new.walk
module01.rb

 1行目から5行目のように、モジュールの定義はクラスの定義に似ています。「module」〜「end」間にdef式によってメソッドを定義していきます。

 8行目ではRabbitクラスにWalkableモジュールをMixinしています。Mixinするためには、クラス定義の中で「include Walkable」のように書きます。こうすることで、RabbitクラスにWalkableモジュールがMixinされます。12行目も8行目と同様、こちらはDuckクラスにWalkableモジュールをMixinしています。Hamsterクラスも同様です。

 19行目から21行目では、Mixinしたモジュールに定義されたメソッドがきちんと使えるかを確かめています。では、実行結果を見てみましょう。

$ ruby module01.rb 
てちてち
てちてち
てちてち

 「てちてち」と3回出力されており、「rabbit.walk」「duck.walk」「hamster.walk」で、Walkableモジュールに定義されたwalkメソッドが呼び出されていることが分かります。

 このように、モジュールをMixinすることで、クラス間で共通する「振る舞い(メソッド)」を部品化して再利用できます。

モジュールに定義されたメソッドのオーバーライド

 継承においてメソッドのオーバーライドが可能でしたが、Mixinしたモジュールのメソッドの定義をオーバーライドすることもできます。

 ウサギが「てちてち」と歩くのも少しおかしい感じがしますので、Rabbitクラスではwalkメソッドをオーバーライドすることにしましょう。

module Walkable
  def walk
    puts "てちてち"
  end
end
 
class Rabbit
  include Walkable
 
  def walk
    puts "ぴょこぴょこ"
  end
end
 
class Duck
  include Walkable
end
 
class Hamster
  include Walkable
end
 
Rabbit.new.walk
Duck.new.walk
Hamster.new.walk
module02.rb

 10〜12行目で、Rabbitクラスの中でwalkメソッドをオーバーライドしています。

$ ruby module02.rb 
ぴょこぴょこ
てちてち
てちてち
module02.rbの実行結果

 module02.rbの実行結果から、RabbitクラスがWalkableモジュールをMixinしているにもかかわらず、「walk」メソッドをオーバーライドしているので、「rabbit.walk」を呼び出した時だけ「ぴょこぴょこ」が出力されていることが分かります。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。