my.code();

my.code();

  • Python
    • 1.環境構築と基本思想
    • 2.基本構文とデータ型
    • 3.リスト、タプル、辞書、セット
    • 4.制御構文と関数
    • 5.モジュールとパッケージ
    • 6.オブジェクト指向プログラミング
    • 7.ファイルの入出力とコンテキストマネージャ
    • 8.例外処理
    • 9.ジェネレータとデコレータ
  • Ruby
    • 1.rubyの世界へようこそ
    • 2.基本構文とデータ型
    • 3.制御構造とメソッド定義
    • 4.すべてがオブジェクト
    • 5.コレクション (Array, Hash, Range)
    • 6.ブロックとイテレータ
    • 7.クラスとオブジェクト
    • 8.モジュールとMix-in
    • 9.Proc, Lambda, クロージャ
    • 10.標準ライブラリの活用
    • 11.テスト文化入門
    • 12.メタプログラミング入門
  • C++
    • 1.C++の世界へようこそ
    • 2.型システムとメモリ
    • 3.関数と参照
    • 4.ポインタと動的メモリ
    • 5.クラスの基礎
    • 6.クラスを使いこなす
    • 7.継承とポリモーフィズム
    • 8.テンプレート
    • 9.STL ①:コンテナ
    • 10.STL ②:アルゴリズムとラムダ式
    • 11.RAIIとスマートポインタ
    • 12.プロジェクトの分割とビルド

第8章: モジュールとミックスイン(オブジェクト指向の拡張)

Rubyのオブジェクト指向において、クラスの継承は「is-a」(〜である)関係を表現するのに適しています。しかし、「has-a」(〜を持つ)や「can-do」(〜ができる)といった**振る舞い(ビヘイビア)**を複数の異なるクラス間で共有したい場合があります。

他の言語では「インターフェース」や「トレイト」で解決するこの問題を、Rubyはモジュール (Module) と ミックスイン (Mix-in) という強力な仕組みで解決します。

モジュール (module) の2つの役割

module キーワードで定義されるモジュールには、大きく分けて2つの主要な役割があります。

  1. 名前空間 (Namespace): 関連するクラス、メソッド、定数を一つのグループにまとめ、名前の衝突(コンフリクト)を防ぎます。
  2. ミックスイン (Mix-in): メソッドの集まりを定義し、それをクラスに include することで、インスタンスメソッドとして機能を追加します。これはRubyの「多重継承」の代替手段です。

名前空間としてのモジュール

プログラムが大規模になると、異なる目的で同じ名前のクラス(例: Database::User と WebApp::User)を使いたくなることがあります。モジュールは、これらを区別するための「仕切り」として機能します。

名前空間内の要素には、:: (スコープ解決演算子) を使ってアクセスします。

module_example.rb
ruby module_example.rb

ミックスインとしてのモジュール (include)

モジュールの最も強力な機能がミックスインです。これにより、クラスは継承ツリーとは無関係に、モジュールの振る舞い(インスタンスメソッド)を取り込むことができます。

include を使うと、モジュールはクラスの継承チェーン(祖先チェーン)に挿入されます。具体的には、include したクラスのスーパークラスの「直前」に挿入されます。

mix_in_example.rb
ruby mix_in_example.rb

Duck と Airplane は全く異なるクラス(Bird のサブクラスと、Object のサブクラス)ですが、Flyable モジュールを include することで fly メソッドを共有できています。

include vs extend

include と extend は、モジュールのメソッドをどこに追加するかが異なります。

  • include: モジュールのメソッドを、クラスのインスタンスメソッドとして追加します。
  • extend: モジュールのメソッドを、クラスのクラスメソッド(特異メソッド)として追加します。
extend_example.rb
ruby extend_example.rb

アクセスコントロール (public, private, protected)

Rubyのアクセスコントロールは、他の言語と少し異なる振る舞い、特に private の動作に特徴があります。

  • public (デフォルト)

    • どこからでも呼び出せます。レシーバ(object.)を省略しても、明示しても構いません。
  • private

    • レシーバを明示して呼び出すことができません。
    • self. を付けずに、クラス内部(またはサブクラス)からのみ呼び出せます。
    • 主にクラス内部の詳細を隠蔽(カプセル化)するために使われます。
  • protected

    • private と似ていますが、同じクラス(またはサブクラス)の他のインスタンスをレシーバとして呼び出すことができます。
    • オブジェクト同士を比較するメソッドなどで使われます。
access_control_demo.rb
ruby access_control_demo.rb

この例では、transfer (public) が内部で withdraw (private) を呼び出し、引数で受け取った other_wallet の deposit (protected) を呼び出しています。deposit は protected なので、other_wallet. というレシーバを明示しても Wallet クラス内からは呼び出せます。

この章のまとめ

  • モジュールは module キーワードで定義され、名前空間とミックスインの2つの役割を持ちます。
    • 名前空間としては、:: を使って定数やクラスをグループ化し、名前の衝突を防ぎます。
    • ミックスインとしては、include することでモジュールのメソッドをインスタンスメソッドとしてクラスに追加できます。これは多重継承の代わりとなる強力な機能です。
    • extend は、モジュールのメソッドをクラスメソッドとして追加します。
    • public, private, protected でメソッドの可視性を制御します。
    • Rubyの private は「レシーバを指定して呼び出せない」というユニークな制約を持ちます。

練習問題1: カウンター機能のミックスイン

Enumerable モジュール(第6章で少し触れました)のように、include したクラスに便利な機能を追加するモジュールを作成します。

  1. Counter というモジュールを定義してください。
  2. Counter モジュールは count_items(item_to_find) というメソッドを持つものとします。
  3. このメソッドは、include したクラスが items という名前の配列(Array)を返すインスタンスメソッドを持っていることを前提とします。
  4. count_items は、その items 配列内に item_to_find がいくつ含まれているかを返します。
  5. ShoppingCart クラスと WordList クラスを作成し、両方で items メソッドを実装し、Counter モジュールを include して count_items が動作することを確認してください。
practice8_1.rb
ruby practice8_1.rb

練習問題2: protected を使った比較

protected のユースケースを理解するための問題です。

  1. Score クラスを作成します。initialize で @value (得点)をインスタンス変数として保持します。
  2. higher_than?(other_score) という public なインスタンスメソッドを定義してください。これは、other_score (Score の別のインスタンス)より自分の @value が高ければ true を返します。
  3. higher_than? メソッドの実装のために、value という protected メソッドを作成し、@value を返すようにしてください。
  4. higher_than? の内部では、self.value > other_score.value のように protected メソッドを呼び出してください。
  5. 2つの Score インスタンスを作成し、higher_than? が正しく動作することを確認してください。また、protected メソッドである value をインスタンスの外部から直接呼び出そうとするとエラーになることも示してください。
practice8_2.rb
ruby practice8_2.rb