Rubyといったオブジェクト指向言語を学ぶと、メソッドの定義方法としてインスタンスメソッドとクラスメソッドという2通りの定義方法があることを学ぶと思います。しかし、言語自体のガイドブックには「定義方法にインスタンスメソッドとクラスメソッドがある」と書いてあるだけで、大抵その使い分けについては書かれていません。
そういう訳で、このエントリではその使い分けについて少し考えてみたいと思います。理論的に厳密な使い分けを目指すというよりは、そもそも使い分けの検討が全くつかない!という方に向けて、その指針の一助となることを目指します。
目次
インスタンスメソッドとクラスメソッド
インスタンスメソッドとクラスメソッドとはそもそものところ、Rubyといった「オブジェクト指向の考え方」を実装した言語の機能です。その機能がなぜあるのか?というそもそものところは、オブジェクト指向の考え方にさかのぼることになります。
そこで、インスタンスメソッドとクラスメソッドについてRubyを例におさらいしてみましょう。
オブジェクト指向の2つの考え方
オブジェクト指向とはざっくり言うと「オブジェクトの組み合わせでソフトウェアを構成する」という考え方です。そのオブジェクトをどのように作るか?という観点で更に、
- オブジェクトを「クラス」から作り出すクラスベースのオブジェクト指向(Rubyを代表とした大多数のオブジェクト指向言語がこの考え方)
- オブジェクトを「オブジェクト」から作り出すインスタンスベースのオブジェクト指向(JavaScriptがこの考え方)
という2つの考え方に分かれるのですが、このエントリでは前者のクラスベースのオブジェクト指向を単に「オブジェクト指向」と呼ぶことにします。
クラスからオブジェクトを作り出すとはどういうことなのか?(Rubyの場合)
クラスからオブジェクトを作り出すとはどういうことなのか?
これをRubyのコードで書くと以下のコードのような形になります。ここでは作り出すオブジェクトの例として「インスタンスオブジェクト」を作り出すサンプルを挙げますが、言葉として長いため単に「インスタンス」と呼ぶことにします。
「タイトルの情報」と「記事の内容」を、「ブログ記事をあらわすクラスPost」のコンストラクタに渡し、そこから作り出された「インスタンスpost」から「インスタンスメソッドrender」を呼び出し、生成されたHTMLを出力する、というサンプルです。インスタンスはtitleとbodyというデータ(インスタンス変数)を持っており、そのデータを活用して処理を行うインスタンスメソッド、ここではrenderを通じてHTMLの出力を行うという仕事をしています。
このように、データを元にクラスからインスタンスを生成し、インスタンスはそのデータを元にして仕事をする、というのがオブジェクト指向の基本形の考え方です。
インスタンスメソッドとクラスメソッド、インスタンスオブジェクトとクラスオブジェクト
先ほど「インスタンスオブジェクトを作り出す例」と言いましたが、オブジェクトにはもうひとつの形があります。それが「クラスオブジェクト」です。
- インスタンスメソッドはインスタンスオブジェクトから実行できるメソッド
ということから分かる通り、
- クラスメソッドはクラスオブジェクトから実行できるメソッド
ということになります。実際のコード例を挙げるとこんな形になります。今度はコンストラクタに直接データを設定するのではなく、ファイルからデータを読みだしてきて、Postのインスタンスを作成するサンプルです。
Postというクラスに対してfind
というメソッドを実行するように書いているPost.find
という部分が、注目すべきポイントです。
ここまででインスタンスメソッドとクラスメソッドとはそもそも何なのか、ということをおさらいしてみました。
インスタンスメソッドとクラスメソッドの使い分け
さて、このエントリの本当の問題である「インスタンスメソッドとクラスメソッドの使い分け」ですが、これはクラスにどんな仕事をさせるべきかを考えるということに等しいので、つまりクラス設計の問題だと考えることができます。
そこで先ほど述べたオブジェクト指向の基本形の考え方である「データを元にクラスからインスタンスを生成し、インスタンスはそのデータを元にして仕事をする」という原則を振り返ると、
- クラスにどのようなデータを持たせるべきか?
- そのデータを元にどのような処理をさせるべきか?
ということを考えることが、つまりクラス設計であると言えます。
従って、クラスにどのようなメソッドを持たせるか?を考えるときには、まずはインスタンスメソッドを配置することを考えます。使い分け、というよりは、まずインスタンスメソッドで実現できないか?を考えるべきだというのが僕の立場です。
インスタンスメソッドで実現すべき処理をクラスメソッドで実現するとどうなるか?
インスタンスメソッドで実現すべき処理をクラスメソッドで実現すると以下のようになります。先ほどのHTML化処理をクラスメソッドにしてみました。
先ほどと何が違うのか?というと、
- クラスはデータを持っていない
- renderメソッドに引数が出現した(titleとbody)
という部分が違います。データは恐らく、Postクラス以外のところで管理されるか、もしくはスクリプトを実行するときの引数なのでしょう。クラスがデータを管理してくれなくなった分、自分でデータを管理する必要が出てきます。
ここで先ほどのファイルからデータを読みだしてくる処理を加えてみましょう。すると、こうなります。
クラスの外側でデータを受け渡しする必要が出てきました。クラスで情報を管理していたときには、こんな煩雑なことをする必要はありませんでした。
更に、記事のデータの種類が増えた場合はどうなるでしょうか?
renderの引数がどんどん増えていきますね。データの受け渡しがどんどん複雑になっています。ではクラスメソッドという実装方針をやめてみましょう。どうなるでしょうか?
クラスの外側でのデータの受け渡しはなくなり、データの受け渡しはクラス内部で行われるようになりました。クラスの中で十分な情報を蓄えているため、renderメソッドは引数を取る必要がありません。
この改善によって何が嬉しいのか。ファイルから記事データを読み込んでHTMLにする、という仕事をするときに、記事データとして何を渡さなければいけないか?を考えなくて済む、というポイントが一番のメリットです。クラスメソッドでデータを受け渡していたときには、記事の構成要素を把握した上で、renderメソッドに引数として渡す必要がありました。
引数があるよりは、ない方が良いのです。引数がなければ、そもそも何を情報として渡さなければいけないかを考えなくて済みます。引数があることで、コードを使う人に余計な仕事をさせることになります。
どのようなときにクラスメソッドを用いるべきか?
反対に、どのようなときにクラスメソッドを用いれば良いのでしょうか?
上記の例で言えば「外部からデータを取得する処理」が一番多いシチュエーションだと思います。
- DBからデータを読みだして、新たにインスタンスを生成する
- ファイルからデータを読みだして、新たにインスタンスを生成する
- オブジェクトを参照して、新たにインスタンスを生成する
一般にこのようなメソッドをファクトリメソッドと呼びます。インスタンスを生成する光景をファクトリ、工場に見立ててそう呼んでいます。
また、Rubyを使っている方ならattr_accessor
といった、クラスの定義に直接記載するコードを見たことがあると思います。このようなコードはクラスアノテーションと呼ばれています。このクラスアノテーションを実現するためには、クラスメソッドとして定義する必要があります。
もしくは、全くデータを持たないクラスを設計するときに、メソッドを全てクラスメソッドとして定義する場合があるでしょう。ただ、こういった場合はRubyではモジュールという機能を使う方が自然だと思います。
まとめ
- データを元にクラスからインスタンスを生成し、インスタンスはそのデータを元にして仕事をする、というのがオブジェクト指向の基本形の考え方
- インスタンスメソッドとクラスメソッドの使い分け、というよりは、まずインスタンスメソッドで実現できないか?を考えるべき
- クラスメソッドの用途としては以下のようなケースがある
- ファクトリメソッドとしての用途
- クラスアノテーションとしての用途
最後に、オブジェクト指向という考え方に立ったコードの改善について、今まで役に立ったと思える本を挙げます。
オライリージャパン
売り上げランキング: 209,090
Head Firstデザインパターンはそのままズバリオブジェクト指向の本、という訳ではないのですが、デザインパターンを題材にオブジェクト指向の考え方を分かりやすく説明している名著だと思います。中で使用している言語はJavaですが、特に問題なく読めるレベルの記法しか利用していないので、平易に読めると思います。
アスキー・メディアワークス
売り上げランキング: 167,509
Rubyのコード改善の話をするときに、この本は絶対に外せないと思います。「Rubyでのリファクタリングで何か参考になる本はないですか?」と言われたら、必ずこの本をおすすめしています。ただ、高いせいか、あまり買ってくれないような気も。。凄く良い本なんですけどね。。