前提・実現したいこと
クラスメソッドとインスタンスメソッドの使い分けを理解していないのですが、
使い分けの方法としてインスタンス先のメンバ変数を参照するか否かで使い分けるのがいいのでしょうか?
もし、上記の認識が正だとするとインスタンス先のメンバ変数を参照しないクラスであれば
そのクラス内の関数は全てクラスメソッドとして定義するのがベストなのでしょうか?
また、それぞれのメリットデメリットがあるなら教えてください。
よろしくお願いいたします。
該当のソースコード
// クラスメソッド class ClassMethod { class func classMethod() { // 処理内容 } }
// インスタンスメソッド class InstanceMethod { let test = "" func instanceMethod() { // 処理内容 } }
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/16 15:25
2020/06/16 16:57
回答1件
0
もしかすると「インスタンス先のメンバ変数を参照するか否か」みたいな実装的なものよりは、境界が曖昧になるのですけれど「その機能がインスタンスに着目したものか、型に関するものか」みたいな意味的なドメイン・文脈で判断するのがよかったりするかもしれません。
あくまでも個人的な感覚なので、ひとつの参考として捉えてもらえると幸いです。
例えば・・・
例えが少し無理やりになるのですけれど、ボードゲームのプレイヤーを実装していたとして、ユーザーがマニュアル操作するプレイヤー Human
と、コンピューターが一様なランダムで手を打つ Random
とがあったとします。
クラスの構造
どちらのプレイヤーであるかは実行時のインスタンスによって切り替えられるとすると、次のようにプロトコル Player
を作ってそれを両者に準拠させるのが良いと思います。
swift
1protocol Player { 2} 3 4class Human : Player { 5} 6 7class Random : Player { 8}
このとき、プレイヤーが "Manual"
操作なのか "Auto"
操作なのかを示す mode
プロパティーと、次の一手を決める move
メソッドを機能として備えることを考えます。
kind プロパティー
まず、操作モードを示す mode
プロパティーについて考えると、その種類はインスタンスではなく肩によって決まってくるので、インスタンス毎にプロパティーで保持しておく必要はありません。
そうすると、クラスプロパティーを使って表現することもできることになるのですけれど、そうしたときの様子に少し無理が出てくるように感じます。この例は際どいところで主観も大きいですが。
swift
1protocol Player { 2 3 static var mode: String { get } 4} 5 6class Human : Player { 7 8 class var mode: String { 9 10 return "Manual" 11 } 12} 13 14class Random : Player { 15 16 class var mode: String { 17 18 return "Auto" 19 } 20}
こうしたときに、どちらのプレイヤーでもインスタンス化して扱えるように Player
型の変数を用意したときに、その操作モードを取得するコードを書いてみます。
swift
1let player: Player = Manual() 2 3print(type(of: player).mode)
この print
文に着目してコードを読んでみると、流れとしては「プレイヤーの型情報を取得して、型情報の操作モードを参照する」みたいな流れになるように感じます。それはそれで間違いはないと思うのですけれど、ここは単純に「プレイヤーの操作モードを参照する」で目的を明確に達成できるので、そういう意味でインスタンスに着目して、たとえ他のプロパティーに依存していなくてもインスタンスプロパティーで実装すると、明瞭なコードになるような気がします。
swift
1protocol Player { 2 3 var mode: String { get } 4} 5 6class Human : Player { 7 8 var mode: String { 9 10 return "Manual" 11 } 12} 13 14class Random : Player { 15 16 var mode: String { 17 18 return "Auto" 19 } 20}
swift
1let player: Player = Manual() 2 3print(player.mode)
move メソッド
次の一手を返す move
メソッドは、もう少し明瞭な違いが見えてきます。
今回の例で想定している move
メソッドは、人が操作する Human
クラスであれば、判断をユーザーに丸投げするので、内部プロパティーに依存しないメソッドになります。そして、一様にランダムで手を決める Random
クラスは、それが純粋にランダムに頼るのであれば内部プロパティーで調整する必要がないため、プロパティーに依存しない作りにすることができます。
swift
1protocol Player { 2 3 static func move() -> Location { get } 4} 5 6class Human : Player { 7 8 class func move() -> Location { 9 10 return ・・・ 11 } 12} 13 14class Random : Player { 15 16 class func move() -> Location { 17 18 return ・・・ 19 } 20}
ただ、このようにすると先ほどの例と似ているのですけれど、実際に使おうとしたときに「型に対して次の手を求める」みたいな、少し違和感のあるコードになってしまう気がします。
swift
1let player: Player = Manual() 2let location = type(of: player).move()
また、今回の例は単純なので簡単に予測できるとは思うのですけれど、将来、パラメーターで難易度調整ができたりするプレイヤークラスが登場したりしたときに、インスタンスメソッドでないと実現できなくなったりします。
そういった観点で「インスタンスとして扱うプレイヤー型としての機能であるなら、インスタンスメソッドとして実装する」のが、コードが無理なく書けて、かつ柔軟性の高いコードになるように感じます。
標準ライブラリーでの使われ方
自分の価値観で述べるだけだと参考にならないかもしれないので、標準ライブラリーでクラスメソッド(静的メソッド)が使われる場面も幾つか紹介しておきますね。
1) 自身のインスタンスを生成する
標準ライブラリーで比較的よく目にするのが、自分自身の型を生成するためのメソッドを導入するのにクラスメソッドやクラスプロパティーが使われる場面があります。
たとえば、UIKit の機能になりますけれど、UIColor
に備えられている標準的な色のインスタンスはクラスプロパティーで実装されています。
swift
1class UIColor { 2 3 class var yellow: UIColor { 4 5 return ・・・ 6 } 7}
これは、何らかの色を表現するインスタンスの機能ではなく、UIColor
という型で表現できる範囲での黄色という意味合いと捉えると理解しやすくなる気がします。コードで書くと次のように「UIColor
の黄色」と読めて明瞭です。
swift
1let color = UIColor.yellow
ちなみにかなり無理やりな余談になるのですけれど、これを無理してインスタンスプロパティーにするとものすごい破綻の仕方をしてくれます。
swift
1class UIColor { 2 3 var yellow: UIColor { 4 5 return ・・・ 6 } 7}
こうしたときには、ダミーの色を用意しないといけなくなり、しかもそれが極めてコードを難読にします。
swift
1let color = UIColor(red: 1, green: 0, blue: 0, alpha: 0).yellow
これと逆に考えて良いのかはわかりませんけれど、仮に逆に考えて良いとしたら、意味的にインスタンスが持つべきメソッドを、プロパティーに依存しないからといってクラスメソッドを使って実装してしまうと、もしかするとこの例みたいに大きな破綻を生んでいたりするかもわかりません。
仮説なので真偽は定かではないですけれど、そんな感じで「意味」に焦点を当ててどちらに実装するかを考えるのは、それなりの価値はある行為のような気がします。
また、この「自身の型のインスタンスを生成するのに、クラスプロパティーを使う」方法は、その型のインスタンスを取ることがわかっている場面で、効果をはっきしてくれたりします。
今回の例では、yellow
をクラスプロパティーで定義することで「UIColor
の yellow
」という意味を持ってくれるので、たとえば view
の backgroundColor
プロパティーが UIColor
型なのであれば、それで表現できる色を取ることが明確なので、次のように型名を省略して色を指定できるという書き方も可能になります。
swift
1view.backgroundColor = .yellow
こういうところからも「意味」に着目していることの一つの現れなのかなって感じたりします。
2) 型が表現する文脈での機能
文字列にみる静的メソッド
先ほどの「意味」に着目した話と同じ用例になると思うのですけれど、演算子の定義にも静的メソッド(≒クラスメソッド)が使われています。
swift
1struct String { 2 3 static func + (lhs: String, rhs: String) -> String { 4 5 return ・・・ 6 } 7}
これは、もちろんプロパティーに依存しないからという理由でも説明はつきますけれど、たとえば「数値としての足し算(加算)」「文字列としての足し算(連結)」みたいに、それが対象とする型(ドメイン)によって動きが違う、同じ名前でもドメインごとに違うもの、みたいな感覚で捉えても、しっくりくるように思います。
浮動小数点数にみる静的プロパティー
他にも例えば、倍制度浮動小数点数での円周率を表現するのにも静的プロパティーが使われています。
swift
1struct Double { 2 3 static var pi: Double { 4 5 return ・・・・ 6 } 7}
こちらは先ほどの UIColor
の例と同じで「倍制度浮動小数点数で表現できる値の円周率」という意味合いと捉えられます。特徴なども UIColor
と同じなので、例えば、2.0
に対して .pi
を掛け合わせれば、それだけでその文脈に適した精度の円周率が使われるという利点があります。
swift
1let answer = 2.0 * .pi
はっきりとした回答を持っていないため、抽象的でわかりにくい回答になってしまっているとは思います。確証がないため自論になっている可能性も否定はできないですけれど「インスタンス先のメンバ変数を参照しないのであれば、クラスメソッドを使えば良い」とは言い切れなそうな感じが伝わってもらえたら本望です。
クラスメソッド(静的メソッド)ならではの特徴があったり、どちらかにすることでコードの意味合い(読め方)が変わってきて読みやすくなったり読みにくくなったりする、そんなあたりも「それぞれのメリット・デメリット」のうちの一例として紹介させてもらったつもりです。
こんな回答が何かのヒントになってくれると嬉しいのですが、いかがでしょうか。
投稿2020/06/16 17:24
総合スコア441
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/16 17:34
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。