RubyとRuby on Railsでメソッド委譲を行う方法【delegate・Forwardable・SimpleDelegator徹底解説】
この記事では、Rubyにおけるメソッド委譲(delegation)について詳しく解説します。
delegateメソッド、Forwardableモジュール、そしてSimpleDelegatorクラスの3つの手法を使いこなせるようになりましょう。
なぜ「委譲」が必要なのか?
オブジェクト指向プログラミングにおいて、クラス同士が協調して動作する方法は主に2つあります。
- 継承(Inheritance)
- コンポジション(Composition)
継承では、親クラスが持つメソッド、定数、インスタンス変数の定義を、それを継承するすべての子クラスが共有するクラス階層を構築します。
例えば、RubyではすべてのオブジェクトがデフォルトでObjectクラスを継承しています。だからこそ、puts、class、object_idといったメソッドにアクセスできるのです。
一方、コンポジションでは、あるクラスが別のクラスのオブジェクトを生成(または受け取り)、そのオブジェクトへ処理を任せます。
例えば、コンピュータは多くのパーツ(オブジェクト)から構成されており、各パーツはそれぞれ得意なことを1つ完璧に行えるように設計されています。画面に何かを表示したいとき、コンピュータはグラフィックカードに「何を表示するか」を伝えるだけで、「どう表示するか」までは指示しません。
これがコンポジションです!
しかし、あなたがコンピュータ側で、グラフィックカードのメソッドへのアクセスを外部に提供したい場合はどうすればよいでしょうか?両者の間に何らかの「橋」を架ける必要があります。その具体的な方法を見ていきましょう。
Rubyでのメソッド委譲 – 実例
ここに2つのクラスがあります。
ComputerMemory
まず、以下のコードをご覧ください:
class Computer
def initialize
@memory = Memory.new
end
end
class Memory
def initialize
@data = []
end
def write(data)
@data << data
end
def read(index)
@data[index]
end
end
computer = Computer.new
ここで、メモリにはComputer経由でのみアクセスできるようにしたいと考えます。これにはさまざまな理由があります:
- メモリユニットは1つしかないため、メモリオブジェクトも1つだけにしたい。アクセスを1つのオブジェクトに集約することで実現できます。
- 誰が(どのアプリケーションが)メモリにアクセスでき、どの領域を利用できるかを制御したい。
- セキュリティやデバッグの目的で、すべてのメモリアクセスをログに記録したい。
そこで、次のように呼び出してみると:
computer.write("rubyguides")
NoMethodErrorが発生して失敗します。Computerクラスにはwriteメソッドが存在しないためです。
undefined method `write' for #<Computer:0x000055c2e8f7d310> (NoMethodError)
Computerクラスにとって、writeが何を意味するのか分からないのです。
ですが……writeメソッドを作成すれば解決します!以下のように書きます:
class Computer
def initialize
@memory = Memory.new
end
def write(data)
@memory.write(data)
end
def read(index)
@memory.read(index)
end
end
リクエストを@memoryオブジェクトへそのまま引き渡しています。これこそがメソッド委譲です。
注意: この方法だと、これらのメソッドが公開インターフェースの一部になります(誰でも呼び出せる状態になります)。必ずしもそれを望まないケースもあるでしょう。
では、メソッド委譲をもっと簡単に行うショートカットは存在するのでしょうか?
あります!早速見ていきましょう。
Forwardableモジュールの使い方
Ruby標準ライブラリに含まれるForwardableモジュールを使えば、委譲メソッドを簡潔に定義できます。
使い方は以下の通りです:
require 'forwardable'
class Computer
extend Forwardable
def_delegators :@memory, :read, :write
def initialize
@memory = Memory.new
end
end
これにより、先ほど手動で作成したメソッドを削除しても、まったく同じ動作を実現できます。
Forwardableはメソッドの引数(ブロックも含みます!)を自動的に処理してくれるため、引数の受け渡しを気にする必要はありません。
とても便利ですね 🙂
Railsのdelegateメソッドの使い方
Railsを使用している場合、あるいはActiveSupport gemを単体で導入している場合は、delegateメソッドを利用できます。
基本的な使い方はこちら:
class Computer
delegate :read, :write, to: :@memory
def initialize
@memory = Memory.new
end
end
delegateメソッドはprefix引数を受け取ることができ、生成されるメソッド名にプレフィックスを付加できます。
例:
class Computer
delegate :read, :write, prefix: "memory", to: :@memory
def initialize
@memory = Memory.new
end
end
この結果、次の2つのメソッドが生成されます:
memory_readmemory_write
メソッド名の由来が明確になるため、可読性の向上にも役立ちます。
SimpleDelegatorですべてを委譲する方法
ここまで紹介したテクニックは、特定のメソッドだけを転送・委譲するためのものでした。
しかし、オブジェクト全体をラップして、そのすべてのメソッドを公開したい場合はどうでしょうか?
そんなときはSimpleDelegatorクラスが活躍します!
使い方は以下の通りです:
require 'delegate'
class CoolArray < SimpleDelegator
end
cool = CoolArray.new([])
cool << "ruby"
cool << "gems"
p cool
これでCoolArrayは、newに渡したオブジェクトと同じように振る舞います。
なぜこれが便利なのでしょうか?元のクラスを一切変更することなく、このオブジェクトに新しいメソッドを追加できる点です。既存のクラスに手を加えられないライブラリのオブジェクトを拡張したい場合に特に有効です。
動画チュートリアル
実際のコードの動きを確認したい方は、以下の動画チュートリアルも参考にしてください。
まとめ
この記事では、Rubyにおけるオブジェクトコンポジションの概念と、複数のテクニックを活用したメソッド委譲の方法を学びました。
- 手動でメソッドを定義する方法
Forwardableモジュールによる宣言的な委譲- Rails / ActiveSupportの
delegateメソッドとプレフィックス機能 SimpleDelegatorによるオブジェクト全体のラップ
それぞれの特徴を理解し、状況に応じて使い分けることで、より保守性の高いコードを書けるようになります。
この記事が役に立ったら、ぜひRuby仲間とシェアしてください。
最後までお読みいただきありがとうございました 🙂
-
Rubyメソッドをスパイする方法!TracePointで実行をトレースしよう
Rubyには、TracePointクラスを使ってアクセスできる組み込みのトレーシングシステムが備わっています。このシステムを利用すると、メソッド呼び出し、新しいスレッドの開始、例外の発生などをトレースできます。 なぜこれを使いたくなるのでしょうか? たとえば、特定のメソッドの実行過程を追跡したい場合に非常に便利です。その処理の中で他にどのメソッドが呼ばれているのか、どんな戻り値が返されているのかを、目で確認できるようになります。 それでは、いくつか具体例を見ていきましょう! メソッド呼び出しをトレースする 多くの場合、TracePointでトレースしたいのはアプリケーション側のコードであり、
-
Rubyの配列(Array)クラスの使い方徹底解説!基本操作と便利なメソッド一覧
配列(Array)とは? 配列はRubyに組み込まれたクラスで、0個以上の要素をリスト形式で保持することができます。さらに、要素の追加・アクセス・繰り返し処理を簡単に行えるメソッドも多数用意されています。 もし配列が存在しなければ、値ごとに個別の変数を用意しなければならず、管理が非常に大変になります。 例: a = 1 b = 2 c = 3 しかし、配列を使えば次のように書けます: numbers = [1, 2, 3] さらに嬉しいことに、配列の中には何でも入れられます! 例えば: 数値 文字列 さらに別の配列!(これは多次元配列になります) それでは、配列を最大限に活用できる