多くの言語に「クラスメソッド」がありますが、Rubyで実装する場合はいろいろ考えることが出てきます。
「クラスメソッド的なメソッド」とは
ここでは、「SomeConst.some_method
」のような形式で呼び出すものを総称して「クラスメソッド的なメソッド」と呼ぶことにします。実際には、多様なものが含まれます。
- クラスインスタンスの特異メソッド(クラスメソッド)
- モジュールインスタンスの特異メソッド
- モジュール関数(厳密には1つの種類ではありませんが、便宜上入れておきます)
- クラスやモジュールのインスタンスに
extend
されたモジュールのメソッド
今回の論題
今回は、「特定のクラスと紐付ける必要のない(or紐付けられない)」クラスメソッド的なメソッドについて考えます。例えば、ファクトリーメソッドやActiveRecordの探索系メソッドは、同じクラスのインスタンスやコレクションを生成するためのメソッドなので、クラスメソッドとするのがいちばん自然だということで、あまり考慮の余地はありません。
また、「定数から呼ぶメソッド」でも、その定数が属するクラスのインスタンスメソッドを呼んでいるだけ(ARRAY_CONST.each
とかSomeModule.methods
など)の場合も考慮から除外します。
各種の作り方
クラスメソッド
クラスメソッドはクラスインスタンスの特異メソッドですが、いくつかの定義方法があります。
- クラスの外側で
def SomeClass.some_method
とする - クラス内で
def self.some_method
とする -
class << self
で特異クラスを開いて、そこでメソッドを定義する
とはいえ、既存クラスに追加するとかそういう特殊な状況以外では、1番目というのはあまり適当ではないと思いますので、いったん置いておきます。
2番目と3番目の違いとして、「1つ1つself
を付ける必要があるかどうか」ということもあるのですが、大きな違いとして「private
にしたい場合」というのがあります。Module#private
は、(引数付きでも引数なしでも)インスタンスメソッドについてprivate
にするかどうかを制御するものなので、def self.some_method
として定義したものには影響せず、別途でprivate_class_method
を実行する必要があります。一方で、3番目のやり方では、特異クラスのインスタンスメソッドとしてクラスメソッドを定義していますので、private
も問題なく適用可能です。
使い道
Javaなど他の言語では「クラスメソッドだけのクラス」が必要になる場面もありますが、Rubyの場合はインスタンス化が不要ならモジュールでかまわないので、純粋にクラスメソッドだけでクラスを構成するのが適切、という場面はそうありません。
一方で、クラスメソッドで複雑な処理をするという状況では、クラスメソッド内でインスタンスを作ってインスタンスメソッドに処理を投げて、インスタンス変数を使えるようにする、という流れが便利な場面もあるかと思います。外から見ればこのインスタンスの存在はわからず、通常のクラスメソッドとして使えます。
class Called
def some_method
# 略
end
def self.some_method(param)
new(param).some_method
end
end
モジュールメソッド
クラスと全く同様に、モジュールにも特異メソッドを定義できます。ただ、「いちいちself.
を付ける or 特異クラスを開いて1段インデントする」というのは煩わしいので、モジュールメソッドがメインになる場合には別な手段を考えたほうがいいかもしれません。
モジュール関数
「モジュール関数」は1つのメソッドではなく、あるモジュールに「public
特異メソッド+private
インスタンスメソッド」があるという、2つセットの状態を指します。ということで、「クラスメソッド的に特異メソッドを呼び出す」のと「include
してprivate
メソッドを関数的に使う」という2つの使い方をできます。
ただ、「クラスメソッド的にしか使わない」ということであればインスタンスメソッドは不要ですが、逆に害になることもありません。とはいえ、性質上特異メソッドの方は自動でpublic
になってしまうので、用途によっては向かないかもしれません。
extend self
Object#extend
はモジュールを引数にとって、特異クラスにモジュールをinclude
する(結果として、モジュールのインスタンスメソッドがextend
先の特異メソッドになる)という動作をするメソッドです。
これを使って、extend self
とすることで、モジュールのインスタンスメソッドを特異メソッドにすることができます。モジュール関数と似ていますが、以下のような違いがあります。
- モジュール関数はメソッド定義に対する操作なので、
module_function
宣言以降に定義されたメソッドにしか適用されないけど、extend self
は特異クラスの操作なので、モジュール内の全メソッドに適用される1 - モジュール関数は「
public
特異メソッド+private
インスタンスメソッド」という組み合わせで作られるけど、extend self
をかけた場合はアクセス制限がそのまま特異メソッドにも引き継がれる
ということで、下請けのprivate
なメソッドが必要な場合にはextend self
のほうが向いているのではないかなと思いました(どうせ、クラスメソッド的に使うためのモジュールをinclude
することもないでしょうし)。
-
もっとも、「一部だけモジュール関数にする」というようなモジュールの運用も通常は考えられませんが。 ↩