【Kotlin】なぜクラスをつかわなければいけないのか?


はじめに

プログラムを勉強しているとクラスオブジェクト指向という言葉をよく目にします。使ったことが無い人も多いと思いますが、そもそも何故クラスやオブジェクト指向が必要なのでしょうか?

結論から言ってしまうとクラスを特に知らず、さらにオブジェクト指向を使わなくてもプログラムを作ることは可能です。むしろ簡単なものであればその方が良い場合もあるぐらいです。

それでは何故クラスやオブジェクト指向を使わないといけないのでしょうか?「そこにオブジェクト指向があるからさ」などと言ってはいられません。この記事を見て頂いているプログラマーの人。オブジェクト指向を使っていますか?使っていなければそれは非常に損をしています。

まずはクラスの使い方を簡単に説明していきたいと思います。

配列を使う

クラスを覚えていく前にまずは準備運動です。複数のデータを扱う場合には配列を使いますよね?例えば配列を使わずに名前を管理する名簿のようなものを作ろうとすると。

sample.kt
  var Name1 = "TARO"
  var Name2 = "HANAKO"

となってしまいます。流石にこんな変数の扱い方はしないよと思っていたら一歩前進しています。
それでは配列を使って書いてみましょう。

名簿を作ります。名簿には名前を管理するName文字列変数と年齢を管理するAge数値型変数を用意します。

sample.kt
    private var Name : Array<String?> = arrayOfNulls(8)
    private var Age : Array<Int?> = arrayOfNulls(8)

    Name[0] = "TARO"
    Age[0]  = 18
    Name[1] = "HANAKO"
    Age[1] = 16
    for (i in 0..2){
        Log.d("Sample","Name = %s Age=%d".format(Name[i],Age[i]))
    }

これで8人まで管理できる名簿が完成しました。2人分の名前と年齢を入れて表示してみました。

データクラス型(レコード型)を使う

Javaではレコード型、Kotlinではデータクラス型とよばれるものを使って見ましょう。

sample.kt
    data class Roster (var Name : String,var Age : Int)
    private var rosters : Array<Roster?> = arrayOfNulls(8)

    rosters[0] = Roster("TARO",18)
    rosters[1] = Roster("HANAKO",16)

    for (i in 0..2){
        rosters[i].let { Log.d("Sample","Name = %s Age=%d".format(it?.Name,it?.Age)) }
    }

配列のときは項目ごとに値を代入するひつようがありましたがデータクラスを使うとまとめて入力が出来るようになりました。
ループ文の let では rousters[i]を参照した物が itに入るのでこのitを使って要素を表示しています。

データクラスはクラスを覚えると使い道があまりありませんので細かい説明は省略します。

クラスを使う

配列、データクラスと理解出来たら意外にもクラスは簡単です。

sample.kt
class RosterClass(aName : String,aAge : Int){
    var Name : String = aName
    var Age : Int = aAge
}
    private val rosterclass : Array<RosterClass?> = arrayOfNulls<RosterClass>(8)

    rosterclass[0] = RosterClass("TARO",18)
    rosterclass[1] = RosterClass("HANAKO",16)

    for (i in 0..2){
        rosterclass[i].let { Log.d("Sample","Name = %s Age=%d".format(it?.Name,it?.Age)) }
    }

定義部分がかなり異なりますが使い方はデータクラスと変わらないことがわかります。

クラスを使うメリット

for文の中で入っている値を返している部分があります。せっかくなのでこれをクラスの特性を活かしたものに書き換えてみます。

sample.kt
class RosterClass(aName : String,aAge : Int){
    var Name : String = aName
    var Age : Int = aAge
    fun toStr() : String{
        return "Name = %s Age=%d".format(Name,Age)
    }
}
    private val rosterclass : Array<RosterClass?> = arrayOfNulls<RosterClass>(8)

    rosterclass[0] = RosterClass("TARO",18)
    rosterclass[1] = RosterClass("HANAKO",16)

    for (i in 0..2){
        rosterclass[i].let { Log.d("Sample",it!!.toStr()) }
    }

これまでメインのfor文の方で項目を文字列にしていましたがクラスではクラス内に関数(メソッド)を作る事が出来ます。
ただ単にプログラムを書く場所が変わっただけで、プログラムを書く手間も量も変わってないじゃないか?と言われそうですが、これがクラスの世界に踏み込む大事な一歩となるのです。

プログラムが先かデータが先か

クラスを使ってこなかった開発者は共通して「先にプログラムがあり、そこにデータが付いてくる」と言った書き方をしていると思います。これは間違ってはいません。しかしクラスを使う場合はこれが「先にデータがあり、そのデータのためにプログラムが付いてくる」という真逆の考え方が生まれてくるのです。

さきほどのプログラムにさらに年齢を書き換える処理を追加してみます。その処理に年齢があり得ない値の場合は代入せず、ログにその旨を出力する処理を追加関数のadd、変更関数のcngを作って書いてみます。2つの関数の引数として添え字と年齢が渡される物とします。

sample.kt
    private fun add(Idx : Int,aAge : Int){
        if (aAge < 0){
            Log.d("Sample","Age error")
            return
        }
        if (aAge > 128){
            Log.d("Sample","Age error")
            return
        }
        rosterclass[Idx]?.Age = aAge
    }
    private fun cng(Idx : Int,aAge: Int){
        if (aAge < 0){
            Log.d("Sample","Age error")
            return
        }
        if (aAge > 128){
            Log.d("Sample","Age error")
            return
        }
        rosterclass[Idx]?.Age = aAge
    }

素直に同じ処理を書きましたが「いやいや普通はこれを共通処理とするでしょ?」となりますよね。ではそれをどこに書くでしょうか?MainActivityでしょうか?他のフラグメントでも書き換える場合はどうでしょうか?、それならグローバル関数にしますか?
こういう悩ましい問題をクラスでは

sample.kt
class RosterClass(aName : String,aAge : Int){
    var Name : String = aName
    var Age : Int = aAge
    fun toStr() : String{
        return "Name = %s Age=%d".format(Name,Age)
    }
    fun Check(aAge : Int){
        if (aAge < 0){
            Log.d("Sample","Age error")
            return
        }
        if (aAge > 128){
            Log.d("Sample","Age error")
            return
        }
        Age = aAge
    }
}

と定義しておけば

sample.kt
  rosterclass[0].Check(100) 

メインの処理はこれだけで済むことになります。

最後に

これだけでクラスのメリットを説明できたわけではありません。
ですがクラスを使うとこういうことが出来ることが理解出来たのでは無いでしょうか?