Rubyのオブジェクト指向におけるクラスとモジュール、継承、Mixin、アクセス制御の使い方:若手エンジニア/初心者のためのRuby 2.1入門(7)(5/5 ページ)
オープンソースのオブジェクト指向プログラミング言語「Ruby」の文法を一から学ぶための入門連載。最新版の2.1に対応しています。今回は、Rubyのにおけるクラスとモジュール、継承、Mixin、アクセス制御などの基本的な使い方について解説します。
比較演算子をつかさどるComparableモジュールに見るMixinの強力さ
Rubyの組み込みライブラリには、「Comparable」モジュールがあります。Comparable、つまり「比較できるもの」を表すモジュールです。
Comparableモジュールは、ComparableモジュールをMixinするクラスに「<=>」メソッドが定義されていることを期待しています。クラスに「<=>」メソッドが定義されていると、Comparableモジュールはクラス側の「<=>」メソッドの結果を使って、「<」「>」「<=」「>=」「==」といったメソッドを自動的にクラスに追加してくれます。つまり、ComparableモジュールをMixinして1個のメソッドを定義するだけで、クラスの機能が大幅に強化されるのです!
では、rabbit.rbに戻ってComparableモジュールをMixinしてみましょう。
class Rabbit include Comparable attr_accessor :name attr_reader :color, :length_of_ears @@count = 0 DEFAULT_NAME = "usachan" DEFAULT_COLOR = :white DEFAULT_LENGTH_OF_EARS = 10 DESCRIPTION = "ウサギは特徴的な耳を持つ可愛い動物です。" def initialize(name: DEFAULT_NAME, color: DEFAULT_COLOR, length_of_ears: DEFAULT_LENGTH_OF_EARS) @name = name @color = color @length_of_ears = length_of_ears @@count += 1 end def jump puts "pyon! pyon!" end def pound_steamed_rice_into_rice_cake puts "pettan! pettan!" end def say_name puts "Hello, I'm #{@name}!" end def print_ears puts "∩_∩" end def print_description puts DESCRIPTION end def to_s "名前: #{@name}, 毛の色: #{@color}, 耳の長さ: #{@length_of_ears}, 全体でのウサギの数: #{@@count}" end def <=>(other) @length_of_ears <=> other.length_of_ears end end
2行目でComparableモジュールをMixinしており、46行目から48行目の間に「<=>」メソッドの定義を追加しました。耳の長さで比較することとして、「<=>」メソッドの中で、インスタンス変数「@length_of_ears」と比較対象のオブジェクト「other」の「length_of_ears」を「<=>」メソッドで比較しています。
require_relative "rabbit" rabbits = Array.new.tap do |array| [7.0, 1.0, 5.0, 1.0].each do |length| array << Rabbit.new(length_of_ears: length) end end rabbits.each_with_index do |rabbit, i| puts "rabbit#{i}: #{rabbit.length_of_ears}" end puts "rabbits[0] < rabbits[1]: #{rabbits[0] < rabbits[1]}" puts "rabbits[0] > rabbits[1]: #{rabbits[0] > rabbits[1]}" puts "rabbits[1] == rabbits[3]: #{rabbits[1] == rabbits[3]}" puts "rabbits[1] <= rabbits[3]: #{rabbits[1] <= rabbits[3]}" puts "rabbits[0] <= rabbits[1]: #{rabbits[0] <= rabbits[1]}" puts rabbits.sort
main06.rbでは、まず3行目から7行目の間で、変数「rabbits」に「7.0」「1.0」「5.0」「1.0」の耳の長さを持つRabbitクラスのオブジェクトの配列を代入しています。
ここでさりげなく「tap」メソッドを使っています。「tap」メソッドはObjectクラスに定義されているメソッドで、「tap」メソッドに渡すブロック変数に、そのオブジェクトそのものが入ります。
また、「tap」メソッドの返り値はそのオブジェクト自身です。ここでは、「Array.new」で生成した空の配列に対して「tap」メソッドを呼び出し、その配列に対してRabbitクラスのオブジェクトを追加しています。
9行目から11行目では、「rabbits」に格納された配列の内容を確認しています。
13〜17行目は、ComparableモジュールをMixinしたことで、きちんと各種比較演算子が定義されているかをチェックしている部分です。
また、比較メソッドが定義されたことによって、Rabbitクラスのオブジェクトを含む配列に対してsortメソッドを使えるようになりました。19行目は実際に耳の長さで配列をソートしています。
では、main06.rbの実行結果を見てみましょう。
$ ruby main06.rb rabbit0: 7.0 rabbit1: 1.0 rabbit2: 5.0 rabbit3: 1.0 rabbits[0] < rabbits[1]: false rabbits[0] > rabbits[1]: true rabbits[1] == rabbits[3]: true rabbits[1] <= rabbits[3]: true rabbits[0] <= rabbits[1]: false 名前: usachan, 毛の色: white, 耳の長さ: 1.0, 全体でのウサギの数: 4 名前: usachan, 毛の色: white, 耳の長さ: 1.0, 全体でのウサギの数: 4 名前: usachan, 毛の色: white, 耳の長さ: 5.0, 全体でのウサギの数: 4 名前: usachan, 毛の色: white, 耳の長さ: 7.0, 全体でのウサギの数: 4
実行結果の6〜10行目で、各種比較演算子が耳の長さを用いて結果を返していることを確認してください。また、11〜14行目では配列rabbitsをソートし、ソートした順にオブジェクトのto_sメソッドが呼ばれています。ここで耳の長さが昇順になっていることから、きちんとソートができていることが分かります。
継承とMixinの使い分け――is-a関係とhas-a関係
ここまで、クラスの定義から継承、モジュール定義からMixinまで説明してきましたが、結局継承とMixinは、どのように使い分ければいいのでしょうか。
継承は「is-a関係」を表す場合に使われます。つまり、「ウサギやアヒルは動物である、ニンジンは野菜である」といった具合です。
ウサギやアヒルの場合、Animalクラスを継承する形でRabbitクラスやDuckクラスを構成するのが自然でしょう。つまり、「ウサギやアヒルは、息をして歩くなど、動物が持つ特徴を全て持っている」という意味です。息をする、歩くなどの共通の振る舞いをAnimalクラスにまとめ、その派生クラスでウサギ独自の振る舞いやアヒル独自の振る舞いを記述するわけです。
Mixinは、「has-a関係」を表す場合に使うとよいでしょう。というのも、物事は「is-a関係」だけで記述できるものではないからです。
例えば、ウサギやアヒルは動物であり、何かを食べることで生きています。一方で食虫植物も虫を食べて栄養の一部としていますが、食虫植物は植物であり、動物というには無理があるでしょう。つまり、動物と植物という違いがあれど、「食べる」という共通の性質を持っている(has)わけです。なので、この食べるという振る舞いをモジュールとして共通化し、AnimalクラスやPlantクラスでMixinすればキレイに部品化できます。
Rubyによる大規模なアプリケーション開発では、クラス設計の問題がいつでも付きまといます。継承とMixinの違いをしっかりと理解して、適切に使い分けることはRubyプログラマーとしての重要なスキルです。
補足「ObjectクラスとKernelモジュール」
ここまで何度か、「Object#tap」など、「Objectクラスにtapメソッドが定義されている」というような説明をしてきました。
しかし実際には、Objectクラスに定義されているメソッド群は、実は「Kernel」モジュールで定義されています。ObjectクラスはKernelモジュールをMixinしているので、tapメソッドなどが使えるわけです。せっかくなので、pryを起動して確かめてみましょう。
[1] pry(main)> Array.new.method(:tap).owner => Kernel
「Array.new」で生成したArrayクラスのオブジェクトに対して、「method」メソッドを呼び出しています。「method」メソッドは、与えられた引数のメソッドオブジェクトを生成します。メソッドオブジェクトに対して「owner」メソッドを呼び出すと、そのメソッドがどのモジュールまたはクラスで定義されているかが分かります。ここでは、Kernelモジュールになっていますね。
[2] pry(main)> Array.new.method(:method).owner => Kernel
ちなみに、「method」メソッドもKernelモジュールで定義されています。「メソッドメソッドメソッド……」と、メソッド続きでちょっとややこしいですね。
メソッドオブジェクトについては、以降の連載で詳しく触れることにします。
次回は、Rubyのメソッドやブロックについて詳しく
以上、簡単にクラスの定義や利用法を説明しました。アクセス制御の説明で述べたように、クラスについて深く理解するためには、さらなる前提知識を必要とします。ですので、以降の連載のメソッドやブロックの解説やメタプログラミングの解説で、クラスの仕組みの深い部分についてあらためて触れることとします。
連載第7回に当たる今回は、クラスやモジュールについて基本的な事柄について解説しました。今回の内容を理解できれば、クラスやモジュールを使ってコードをコンポーネント化できるようになるでしょう。
次回は、今回何げなく定義して何げなく使っていたメソッドやブロックについて掘り下げていきます。メソッドやブロックの仕組みを理解することで、より高度なコードを読み書きできるようになることでしょう。お楽しみに!
- Rubyで逆ポーランド変換機を作りgem作成&コマンドの使い方
- 難しいが強力! Rubyのメタプログラミング、self、特異クラス/メソッド、オープンクラスとモンキーパッチ
- RubyのThread、Fiber、Kernel、forkで並列処理やプロセスの深淵へ
- RubyのFile/IOクラスで入力と出力、ファイルの読み取りと書き込み、フィルター作成
- Rubyの例外とその捕捉――基本のbegin〜rescue〜endからensure、else、retry、後置rescueまで
- Rubyの面白さを理解するためのメソッド、ブロック、Proc、lambda、クロージャの基本
- Rubyのオブジェクト指向におけるクラスとモジュール、継承、Mixin、アクセス制御の使い方
- RubyのNumericとTimeで数値と時間をさまざまな操作・演算・判定
- RubyのString/Regexpクラスによる強力な文字列操作/正規表現
- RubyのRangeクラスと範囲オブジェクト、範囲演算子、イテレーターの使い方
- Rubyの配列、ハッシュテーブルを表現するArray、Hashクラスの使い方
- Ruby 2.1の基本構文/基本文法まとめ&Pryの使い方
- Rubyプログラミングを始めるための基礎知識とインストール
著者プロフィール
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
監修者プロフィール
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 3万5000円の参加費でも内外から580人の参加登録:公用語に英語、「再起動」したRubyKaigi 2013が東京で開催
いったん終了していたRubyコミュニティ主催の年次イベントが再開。技術色、国際色を強め、盛況のうちに幕を閉じた - IT業界 転職市場最前線(48):Web業界、今から行くならRubyエンジニアが狙い目?
不況で冷え込んでいたIT業界の転職市場に、回復の兆しが見え始めている。だが、業種や職種によって採用数や条件に大きな差異が生まれている。転職市場の動向を追い、自身のキャリア戦略立案に生かしてほしい。 - 安定版のメジャーバージョンアップ:Ruby 2.0.0がリリース、大規模化対応の機能などを搭載
生誕20年となる節目を迎えて、プログラミング言語「Ruby」の最新版がリリースされた - 新バージョンで何が変わるのか、Rubyはどこへ向かうのか:まつもと×笹田、Ruby 1.9を語る
- いよいよ始まるRuby 1.9への移行:開発コアメンバが語るRubyの今とこれから(前編)
- Rubyの今後の進化の方向性とは?:開発コアメンバが語るRubyの今とこれから(後編)
- 互換性や脆弱性の問題にどう対応していくのか:Rubyが抱える課題、NaClの前田氏が講演
- Rails Hub情報局:「なんでRubyなんか作った!? 迷惑だ!」に対するMatzの答え
- Rails Hub情報局:Rubyはイノベーション言語として選ばれている
- Rails Hub情報局:Rubyのまつもと氏は、一発屋で終わるのか?
- Rails Hub情報局:Rubyに魔法は要らない
- 数々の“スペル”で高度なプログラミング:Rubyの魔術
- 晴読雨読@エンジニアライフ:『たのしい開発 スタートアップRuby』――なぜRubyistたちはあれほど楽しそうなのか
- Ruby 1.9.3に対応:「JRuby 1.7.0」登場、1年半ぶりのメジャーアップデート
JavaVM上のRuby実装「JRuby」の最新版となる「JRuby 1.7.0」が、10月22日にリリースされた。