Help us understand the problem. What is going on with this article?

クラスメソッド的なメソッドの作り方を考える

More than 3 years have passed since last update.

多くの言語に「クラスメソッド」がありますが、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することもないでしょうし)。


  1. もっとも、「一部だけモジュール関数にする」というようなモジュールの運用も通常は考えられませんが。 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away