はじめに
ruby-jpのSlackで以下のような質問が投稿されていました。
クラスメソッドとインスタンスメソッドの具体的な違いがわかりません。
現状「クラスメソッドはクラスから実行でき全体に関する処理を書くときによく使うもの。インスタンスメソッドはインスタンスから実行でき、個別具体的な処理を書くときに使うもの。」という理解をしています。そして実装の際に「これはクラスメソッドとインスタンスメソッドどちらで書くべきなのか」悩むケースが多いです。
上記を踏まえて質問です。
- クラスメソッドとインスタンスメソッドの具体的な違いを皆さんはどのように定義しているか
- どこからがクラスメソッドでどこからがインスタンスメソッドなのかの境目はどのあたりにあるか
をお伺いしたいです!
クラスメソッドとインスタンスメソッドの使い分けは僕がメンターをやっているフィヨルドブートキャンプでもよく見かける質問です。
そこで僕が考えるクラスメソッドとインスタンスメソッドの使い分けを次のように説明してみました。
なお、これはプログラミング歴があまり長くなく、オブジェクト指向プログラミングも最近勉強し始めたばかり、という初心者さんを対象にしています。
クラスメソッドとインスタンスメソッドの使い分け
初心者向けのざっくりとした指針を示すなら、「クラスメソッドとインスタンスメソッドの使い分けに迷ったら、とりあえずインスタンスメソッドにしておけ」と答えます。
その上で、「もしインスタンスを引数なしでnewした直後に何かメソッドを呼び出している場合は、それはクラスメソッドの候補かもしれない」と考えると良いです。
たとえばこんな感じですね。
# 引数なしでnewした直後にメソッドを呼んでいる Hoge.new.do_something # このコードもやってることは上と同じ hoge = Hoge.new hoge.do_somehting # do_somethingは実はクラスメソッドが適しているかも(100%必ず、というわけではない) Hoge.do_somehting
このとき、do_something
がインスタンス変数(@name
など)や、Railsならモデルの属性(email
メソッドなど)に依存している(=読み書きしている)場合はインスタンスメソッドのままにすべきですが、そうでなければクラスメソッドの方が向いています。
もちろん、細かい話をすると「ケースバイケースなので、まずコードを見せてください」となるのですが、オブジェクト指向初心者さんなら上のような指針で大きく外さないんじゃないかと考えています。
上の説明のもう少し具体的な例
以下は「クラスメソッドでいいのでは?」と思われるメソッドの例です。
calculator = Calculator.new calculator.add(2, 3) #=> 5 calculator.add(4, 5) #=> 9 class Calculator def add(a, b) a + b end end
インスタンス変数を全く使ってないので、この場合は次のようにクラスメソッドにしても問題ないですね。
Calculator.add(2, 3) #=> 5 Calculator.add(4, 5) #=> 9 class Calculator def self.add(a, b) a + b end end
一方、次の場合はどうでしょうか?
calculator = Calculator.new calculator.add(2, 3) #=> 5 calculator.add(4, 5) #=> 9 # これまでの計算履歴を返す calculator.history #=> ["2 + 3", "4 + 5"] class Calculator attr_reader :history def initialize @history = [] end def add(a, b) @history << "#{a} + #{b}" a + b end end
これは履歴をインスタンス変数にため込んでいくのでインスタンスメソッドのままにしておく必要があります。
(クラスメソッドとクラスインスタンス変数にすれば・・・という議論もできますが、ややこしいのでここでは無視します)
クラスメソッドが向いているパターン3つ
さらに、RubyやRailsで実際に存在しているクラスメソッド(特異メソッド)を参考にして、「こういうメソッドはクラスメソッド向きであることが多い」というパターン3つ挙げてみます。
いずれも「インスタンスメソッドで定義するとなんか不自然」と思うようなものばかりなので、とりあえずインスタンスメソッドで作ってみて、違和感を感じたらクラスメソッド化を検討する、というようなアプローチでも良いかもしれませんね。
パターン1:関数的なメソッド
ここでいう関数的とは、数学のy = f(x)
を満たすようなメソッド、つまり、入力値x
に対して毎回必ずy
を戻り値として返す特性を指します。
たとえばDate.valid_date?
などが関数的なメソッドに該当します。
# 日付として有効であればtrue、そうでなければfalseを返す Date.valid_date?(2022, 6, 30) #=> true Date.valid_date?(2022, 6, 31) #=> false
もしこれがインスタンスメソッドになっていると不自然に思いませんか?
# dateは今日の日付を表すインスタンス date = Date.today # なんで「今日の日付」が他の日付の妥当性を検証しなきゃいけないの? date.valid_date?(2022, 6, 30) date.valid_date?(2022, 6, 31)
パターン2:新しいインスタンスを生成するメソッド
新しいインスタンスを生成する場合、そもそも「インスタンスがない状態」から出発しなきゃいけないので、自ずとクラスメソッドになります。
# 文字列をパースしてDateオブジェクトを生成する Date.parse("2022-06-30") #=> #<Date: 2022-06-30 ((2459761j,0s,0n),+0s,2299161j)>
もしこれがインスタンスメソッドになっていると不自然に思いませんか?
# dateは今日の日付を表すインスタンス date = Date.today # なんで「今日の日付」が他の日付を生成しなきゃいけないの? date.parse("2022-06-30")
ちなみにnew
メソッドも「新しいインスタンスを生成するクラスメソッド」と考えることができますね。
# newも無から新しいインスタンスを生成するクラスメソッド Date.new #=> #<Date: -4712-01-01 ((0j,0s,0n),+0s,2299161j)>
Railsのfindメソッドやscopeなんかもその一種だと考えられます。
# DBにアクセスして新しいインスタンスを生成(復元)する User.find(id) #=> (指定したidのDB上のUserが返る) User.all #=> (DB上の全Userが返る) User.active #=> (activeなDB上のUserが返る)
パターン3:そのクラスの複数のインスタンスを扱うメソッド
これはRubyやRailsのメソッドで該当するものが見当たらなかったので、架空のメソッドで説明します。
# carsのデータをExcelファイルに出力する(架空のメソッド) Car.export_as_xlsx(cars)
もしこれがインスタンスメソッドになっていると不自然に思いませんか?
# carは「ぷりうす」を表すインスタンス car = Car.new('ぷりうす') # なんで「ぷりうす」が他のcarのデータをExcel出力しなきゃいけないの? car.export_as_xlsx(other_cars)
このほかにも「DSLのように振る舞うクラスメソッド(例: Railsの belongs_to
や has_many
等)」もありますが、初心者が自分で定義することは少ない&インスタンスメソッドにするのがそもそも困難だと思うのでここでは割愛します。
RubyやRailsのクラスメソッドを分析すると勉強になるかも
今回はRubyやRailsが提供しているメソッドを中心に取り上げましたが、僕が業務でクラスメソッドを定義する場合もだいたいこのパターンのいずれかに合致しているように思います。
今回やったようにRubyやRailsのAPIドキュメントを眺めて、どういう目的でクラスメソッドが定義されているのかを自分なりに分析したりすると、クラスメソッドの使いどころがだんだんとわかってくるかもしれません。
まとめ
というわけで、今回は僕が考えるクラスメソッドとインスタンスメソッドの使い分けについて説明してみました。
オブジェクト指向的に適切なクラス設計がされていれば、クラスメソッドが登場する回数はインスタンスメソッドに比べてずっと少ないはずです。
一方、オブジェクト指向らしくないクラス設計だと、「クラスメソッドでもインスタンスメソッドでもどっちでも構わない」というメソッドが増えやすくなります。特に、オブジェクト指向に不慣れな人が作ったクラスは、すべてクラスメソッドに置き換え可能(関数的メソッドばかり)というケースもよくありますね。
ただ、このあたりの考え方はなかなか独学で身に付けるのはなかなか難しいので、先輩プログラマにコードレビューを受けたりしながら学ぶのが一番よかったりします。僕自身も初心者だった頃は先輩プログラマの指導を受けながらオブジェクト指向の考え方を学びました。
なので、身近に頼れる先輩がいる初心者さんは、積極的にコードレビューを依頼していきましょう!
あわせて読みたい
かなり昔に書いた記事で対象言語もJavaが中心ですが、僕が昔読んだオブジェクト指向関連の技術書を以下のエントリにまとめています。
あと、こちらも古い記事ですが、オブジェクト指向関連で特に役に立った書籍を数冊ピックアップして紹介しています。
【PR】フィヨルドブートキャンプならコードレビューしてもらえるよ
僕がメンターをやっているフィヨルドブートキャンプにはオブジェクト指向プログラミングのプラクティスも用意されています。
学習内容 | FJORD BOOT CAMP(フィヨルドブートキャンプ)
僕を含む経験豊富なメンターが受講生のみなさんが書いたプログラムをしっかりコードレビューするので、身近に頼れる先輩がいない場合はフィヨルドブートキャンプで学習することも検討してみてください😃
【PRその2】「プロを目指す人のためのRuby入門」もよろしくお願いします
オブジェクト指向を専門に扱う本ではありませんが、拙著「プロを目指す人のためのRuby入門(通称・チェリー本)」でも自作クラスの作り方を丁寧に説明しています。
そもそもクラスを定義する構文やRubyの言語機能がよくわかっていない、という方はぜひ「プロを目指す人のためのRuby入門」でRubyについて学んでみてください!