Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

  1. トップページ
  2. Swift入門編 - 関数とクラスの使い分けについて

関数とクラスの使い分けについて

みなさまどうも。

これまでの記事でざざっとどどっとクラスとかインスタンスとかについて解説してきました。しかし、肝心な概念とか使い方とか使い分けとかそういうところの解説がまだでしたので、今回は初心者の方がハマりがちなその部分についてやっていきましょう。

「関数とクラスの使い分け」とか「クラスという概念の利便性」とかがよく理解できなくて何年も悩んでしまってる、という方も実際多いので結構重要な項目になりますね。

これは関数で作った処理と、クラスで作った処理を見比べてみるのが手っ取り早いので以下のサンプルを見てみて下さい。同じような処理を関数のみとクラスのみで作ってみました。

func testFn() {
    let n = 1 + 1

    print(n)
}

testFn() // 『2』が出力されます。

class TestClass {
    let n : Int

    init() {
        n = 1 + 1
    }

    func m() {
        print(n)
    }
}

let testClassA = TestClass()

testClassA.m() // 『2』が出力されます。

上記の処理はどちらも『1 + 1』を演算して、その結果である数値『2』を出力しているだけの処理ですね。

そんなに変化がないような印象を受けるかもしれませんが、上記の処理で大きな違いが一点あります。どこか分かりますでしょうか。この時点で理解できた方はかなり頭の回転が早いです。

どちらも「効率よく目的を達成するために処理をまとめておく」という根本的なところは同じですが、処理を実行させる流れに大きな違いがあります。『1 + 1』という演算に注目してみてください。

func testFn() {
    let n = 1 + 1 // ここに注目です。

    print(n)
}

testFn() // 『2』が出力されます。

class TestClass {
    let n : Int

    init() {
        n = 1 + 1 // ここに注目です。
    }

    func m() {
        print(n)
    }
}

let testClassA = TestClass() // インスタンスを生成します。

testClassA.m() // 『2』が出力されます。

では答えです。

答えは「関数の方は実行されるたびに『1 + 1』の演算をしてその結果を出力する処理になっているが、クラスの方はインスタンスの生成時のみ『1 + 1』の演算を行い、その結果を内部のプロパティである定数『n』に保存し、その後は定数『n』を出力するだけの処理になっている」といった感じです。

長ったらしいので先程のサンプルにコメント書いてみました。

func testFn() {
    let n = 1 + 1 // 関数が呼び出されるたびにこの演算が行われます。

    print(n) // 定数『n』を出力します。
}

testFn() // 『1 + 1』の演算をして、その結果を定数『n』に代入して、その代入された定数『n』を出力します。

testFn() // 『1 + 1』の演算をして、その結果を定数『n』に代入して、その代入された定数『n』を出力します。

testFn() // 『1 + 1』の演算をして、その結果を定数『n』に代入して、その代入された定数『n』を出力します。

class TestClass {
    let n : Int

    init() {
        n = 1 + 1 // インスタンス生成時のみ演算を行います。
    }

    func m() {
        print(n) // 定数『n』を出力します。
    }
}

let testClassA = TestClass() // 『1 + 1』の演算結果を定数『n』に代入したインスタンスを作ります。

testClassA.m() // 定数『n』を出力します。

testClassA.m() // 定数『n』を出力します。『1 + 1』の演算結果は内部プロパティの定数『n』に保存済みですので足し算はしてません。CPUに優しいです。

testClassA.m() // 定数『n』を出力します。『1 + 1』の演算結果は内部プロパティの定数『n』に保存済みですので足し算はしてません。CPUに優しいです。

このように「関数は内部にデータの保持ができない」というところと「クラスはインスタンスの内部にデータを保持することができる」というところが関数とクラスの大きな違いになりますね。

最近のデバイスは昔と比べると処理速度が桁違いに早いです。処理速度が早いということは莫大なデータも生成することができるようになっているという事になるので、クラスのように内部にデータを保持できる機能があると非常に構築が楽になります。

例えば、ファイナルファンタジーシリーズの最新作のような描画が美しい3Dゲームの「人間キャラクター」を生成する処理を構築することになったとしましょう。

3Dゲームの「人間キャラクター」を生成する演算って結構すごい処理量になります。大きさとか形を決めて動作とかを登録してスキンにテクスチャを貼って周りの状況に合わせて影を作ったり髪をなびかせる物理演算とかを行って...といった感じですね。

その生成したデータはどこかに保存しておかないと処理が追いつかなくなってしまいます。そんなときにインスタンスにデータを保持できるクラスを使って構築するととってもスマートになります。

さらにクラスはインスタンスを作ってから処理を行うという形になっていますよね。なのでそもそもの構造が「複製しやすいようになっている」というのも強力なところです。

「人間キャラクター」は沢山登場させることになると思うので、「インスタンスを作成してから処理させる」というクラスの基本的な構造との相性は非常に良かったりします。

関数とクラスの違いはこんな感じになりますね。

この使い分けについては特別正解はなく、結構好みが別れるところでもあります。正直どちらを使っても実装できるという場合が多かったりするので、お好きなように実装されてしまって下さい。現場の先輩に合わせるも良し、我が道を行くのもそれもまた良しでございます。

あと、クラスという機能は「必須機能」ではなく「便利機能」になります。「別にクラスという機能がなくてもほぼ全ての処理の構築は可能である」ということも一応覚えておいて下さい。

というのも、昨今のプログラム言語の元になったC言語って子がいます。このC言語くんは物理レベルから表層レベルまでほぼ全ての処理が構築可能だったりする優秀な子です。よほど特殊なものでない限りは大体イケる感じです。

しかし、このC言語くんにはクラスという機能は実装されていなかったりします。なのにほぼ全ての処理を構築できる、ということを考えるとクラスは「必須機能」ではなく「便利機能」であるということですね。

というわけで先程の『1 + 1』のサンプルの処理をこれまで紹介してきた関数の記法のみを使って以下のように変えると同じような構造で処理を構築することができます。

func fInit() -> Int { // 『1 + 1』の演算結果を返す初期化用の関数を定義します。
    return 1 + 1
}

func f(num: Int) { // 引数を出力する関数を定義します。
    print(num)
}

let n = fInit() // 『1 + 1』の演算結果を定数『n』に保存します。

f(num: n) // 定数『n』を出力します。『1 + 1』の演算は行われていません。

ただ、上記のように関数のみで処理を構築すると各処理がバラバラになってしまいがちなんで管理がしづらいんですよね。クラスと見比べてみるとよく分かると思います。

// 関数で構築したパターン ここから

func fInit() -> Int { // 『1 + 1』の演算結果を返す初期化用の関数を定義します。
    return 1 + 1
}

func f(num: Int) { // 引数を出力する関数を定義します。
    print(num)
}

let n = fInit() // 『1 + 1』の演算結果を定数『n』に保存します。こんな感じでどこかに『1 + 1』の演算結果を保存しておくのがミソです。

f(num: n) // 定数『n』を出力します。『1 + 1』の演算は行われていません。

// 関数で構築したパターン ここまで

// クラスで構築したパターン ここから

class TestClass {
    let n : Int

    init() {
        n = 1 + 1 // インスタンス生成時のみ『1 + 1』の演算を行い、その結果を保存します。
    }

    func m() {
        print(n) // 定数『n』を出力します。
    }
}

let testClassA = TestClass() // 『1 + 1』の演算結果を定数『n』に代入したインスタンスを作ります。

testClassA.m() // 定数『n』を出力します。

// クラスで構築したパターン ここまで

多分ほとんどの方が「クラスのほうが管理がしやすそう」と感じて頂けたのではないかと思います。

まあ言ってしまえば「処理とデータを保存する場所の違い」といったところなんですが、複雑な処理を構築する場合はこういうところがメンテナンスのしやすさに繋がるので中々侮れません。

なので「単純な処理は関数で、処理を複製する可能性があったり演算した結果を保持する必要があったりする場合はクラスで」というイメージを持って構築すると良いかもですね。

というわけで以上になります。概念的なお話になってしまいましたが少しでも関数とクラスの違いが感じて頂ければ幸いでございます。ではではこの辺で失礼致します。

(´-`).。oO(これから先の記事は現在執筆中でございます...)

(´-`).。oO(お待たせしてしまって申し訳ございません...)

この記事は桜舞が執筆致しました。

著者が愛する小型哺乳類

桜舞 春人 Sakurama Haruto

ISDN時代から様々なコンテンツを制作しているちょっと髪の毛が心配な東京在住のプログラマー。生粋のロングスリーパーで、10時間以上睡眠を取らないと基本的に体調が悪い。好きなだけ寝れる生活を送るのが夢。ゲームとスポーツと音楽が大好き。誰か髪の毛を分けて下さい。

記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。