カテゴリー
Kotlin言語

ゼロから始めるAndroidアプリ(Kotlin編)Unit2 – 2 第2回 後半:何でもオブジェクトで考える

Android Developers > スタートガイド > Kotlin と Android をゼロから学ぶ > Unit2 – レイアウトUnit2-2 第2回 後半 の内容をまとめたものです(翻訳ではありません)


Unit2 – 2 第2回 後半

第2回 後半 の内容は以下の通りです

  • Kotlinでクラスや継承を使ってプログラムを作成します
  • データについても、データの枠組みをクラスで作成してインスタンスという形で用意していきます

Kotlinで注文プログラムを書いてみる

作成するのは次のようなプログラムです

麺と野菜の注文を受けると、それを使った料理の注文番号と注文金額が表示されるプログラムを作成します

Item, Noodles, Vegetables クラスを作成する

最初に作るのはこの3つのクラスですね

  • Item クラス・・・注文書のテンプレートになるクラス(親クラス)
  • Noodles クラス・・・麺の注文書を作成するクラス(子クラス)
  • Vegetables クラス・・・野菜注文書を作成するクラス(子クラス

うん、まず Item クラスを作ってね。それを継承した子クラスとして Noodles クラスと Vegetables クラスを作成するの

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10)

class Vegetables : Item("Vegetables", 5)

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  • Item クラス
    • name, price プロパティをもつ(具の名前と価格)
  • Noodles クラス , Vegetables クラス
    • Itemから継承した name と price に値を保持している

実行したらこんな数字が表示されちゃって…

Noodles@5451c3a8
Vegetables@76ed5528

うん、デフォルトのままだとこうなるの

  • Item クラスに続く( )の中はコンストラクタ(プライマリコンストラクタ)になります
    • コンストラクタの中の name と price はクラスのプロパティになります(val や var で宣言されているため)
    • val で宣言されていると初期化後の変更はできません
  • Item クラスは Noodles クラスと Vegitables クラスの親クラスなので、open キーワードを付けて継承を許可しています
  • Noodles クラスと Vegetables クラスは Item クラスを継承しています。親クラスのコンストラクタによって name と price にそれぞれ値が設定されています
  • main() で生成したインスタンスをそのまま表示しています。表示されているのはインスタンスの参照先をあらわす数値です

toString() メソッドをオーバーライドする

クラスの toString() メソッドを書き換えると、もうちょっとちゃんとした表示になります

  • Kotlinでは、どんなクラスにも自動的に toString() メソッドがつきます
  • toString() メソッドのデフォルトは、インスタンスの参照先(オブジェクトの型と参照先のメモリアドレス)です
  • インスタンスを表示しようとすると、インスタンスの toString() メソッドが自動的に呼ばれます
  • toString() をオーバーライドして、この暗号のような出力を、もうちょっと意味のあるものに変更します
  • たとえば先のKotlinコードの Noodles クラスと Vegetables クラスをこのように書き換えてみます
open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}

class Vegetables() : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  • 実行するとこんな風に出力されます
Noodles
Vegetables

すっきりして見やすいです

Vegetables クラスのコンストラクタを変更する

Vegetables クラスの引数(コンストラクタ)で、野菜の種類が指定できるようにしますね

方法1:変数を書き並べる

Vegetables クラスのコンストラクタをこんな風に書き換えます

class Vegetables(val topping1: String,
                 val topping2: String, 
                 val topping3: String) : Item ("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}

このクラスからインスタンスを生成するには次のようにします

fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

この場合、野菜の種類は必ず3種類を指定しないといけません

うーん、必ず3つ指定ってなると、ちょっと使いづらくなっちゃうかも…

方法2:リストで渡す

  • 野菜の種類をいくつでも好きな数だけ指定できるように改良します
  • 野菜の種類をリストにして受け渡すようにします
  • Vegetables クラスをこんな風に書き換えます
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}

このクラスからインスタンスを生成するには次のようにします

Vegetables(listOf("cabbage", "sprouts", "onion"))

これなら何種類でも大丈夫ですね!

方法3:vararg を使う

おすすめは vararg なの

  • 引数に vararg をつけると、同じ型の引数を好きな数だけ指定できるようになります
  • 引数にいくつもの変数を並べたり、リストで渡すよりシンプルで使いやすい方法です
  • Vegetables クラスをこんな風に書き換えます
class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}

引数の数はいくつでも構いません。必要な数の引数でインスタンスを生成できます

fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

引数の書き方が自然なんですね

vararg で取得した引数を toString() で表示してみる

toString() メソッドを変更してみますね

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        return name + " " + toppings.joinToString()
    }

}

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  • vararg でまとめて取得したパラメータを toppings 変数で参照しています
  • joinToString() メソッドは、vararg でまとめて取得したパラメータをコンマ(,)区切りで結合した文字列にします
    • joinToString(” “) とするとスペース区切りでまとめられます
  • 実行するとこんな風に出力されます
Noodles
Vegetables Cabbage, Sprouts, Onion

おお~、ちゃんと表示されてます

コンストラクタに何も指定しなかったときの処理を追加する

空っぽの判定には isEmpty() メソッドを使うの

  • Vegetables クラスのコンストラクタに何も指定しないとき、toString() で “Chef’s Choice” と表示されるようにします
  • パラメータの有無は isEmpty() メソッドで判定します
  • こんな風に記述します
open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    val vegetables2 = Vegetables()
    println(noodles)
    println(vegetables)
    println(vegetables2)
}

実行するとこんな風に出力されます

Noodles
Vegetables Cabbage, Sprouts, Onion
Vegetables Chef's Choice

注文を受け付けるクラスを作る

注文書を受け付けて料理の内容や値段を表示する Order クラスを作りますね

今までに作ったクラス

  • Item クラス・・・素材注文書を作成するクラス
  • Noodles クラス・・・麺注文書を作成するクラス
  • Vegetables クラス・・・野菜注文書を作成するクラス

これから作るクラス

  • Order クラス・・・素材注文書を受け付けて、料理の注文にまとめるクラス
    • orderNumber, itemList プロパティ(注文番号と素材注文書リスト)
    • addItem(), addAll(), print() メソッド

Order クラスを作成する

Order クラスを記述します。処理の内容は空のままで大丈夫

class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
   }

   fun addAll(newItems: List<Item>) {
   }

   fun print() {
   }
}
  • addItem() メソッド・・・itemList プロパティ(素材注文書リスト)に素材注文書(Noodles, Vegetables クラスのインスタンス)を追加するメソッド
  • Noodles, Vegetables クラスは Item クラスの子クラスなので、Item型として引数にすることができます

Order クラスのメソッドを記述する

まず addItem() メソッド

  • addItem() メソッドに指定されたインスタンスを itemList リストに追加する処理を記述します
fun addItem(newItem: Item) {
    itemList.add(newItem)
}

addAll() メソッドも同じかんじで

  • addAll() メソッド・・・itemList プロパティ(素材注文書リスト)に、リスト化した素材注文書(Noodles, Vegetables クラスのインスタンスをリストにしたもの)を使ってまとめて追加するメソッド
  • addAll() メソッドに指定されたリストを itemList 変数に追加する処理を記述します
fun addAll(newItems: List<Item>) {
    itemList.addAll(newItems)
}

print() メソッドで注文内容を表示するんですね

  • print() メソッド・・・注文ごとの素材の注文内容と値段、注文の合計金額を出力するメソッド
    1. まず注文番号を出力
    2. それぞれの素材とその値段を出力
    3. 素材リストの金額を累積して、最後に合計金額として出力
fun print() {
    println("Order #${orderNumber}")
    var total = 0
    for (item in itemList) {
        println("${item}: $${item.price}")
        total += item.price
    }
    println("Total: $${total}")
}

ループ処理を使って itemList(リスト)から取り出した item(インスタンス)を表示してるの

インスタンスを表示するときは自動的に toString() が呼ばれるんでしたよね

注文を作成して実行してみる

main() で注文を作成(Order クラスのインスタンスを生成)して実行してみます

  • main() で Order クラスのインスタンスを作成してみます
  • Order クラスのインスタンスを生成するのは、料理の注文を受けたことと考えます
fun main() {
    val order1 = Order(1)
    order1.addItem(Noodles())
    order1.print()

    println()

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    order2.print()

    println()

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    order3.print()
}

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
       itemList.add(newItem)
   }

   fun addAll(newItems: List<Item>) {
       itemList.addAll(newItems)
   }

   fun print() {
       println("Order #${orderNumber}")
       var total = 0
       for (item in itemList) {
           println("${item}: $${item.price}")
           total += item.price
       }
       println("Total: $${total}")
   }
}

実行するとこんな風に出力される

ちゃんと注文一覧がでました!

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

料理注文プログラムを改良してみる

プログラムをさらに改良してみますね

注文された料理をリストにまとめて処理する

MutableList 型の変数に注文(Orderクラスのインスタンス)をまとめて、それを処理するようにしてみますね

難しそうだけどがんばります

  • 注文された料理をすべてリスト(注文料理リスト)にまとめてみます
    • 料理の注文は追加されていくので、注文料理リストには変更が可能な MutableList 型を使います
    • 注文の追加には add() メソッドを使います
  • 注文がリストにまとめられたら、注文内容をループ処理でまとめて表示します
    • 出力を見やすくするのに println() で行をあけるようにしています
fun main() {
    val ordersList = mutableListOf<Order>()
    
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    for (order in ordersList) {
        order.print()
        println()
    }
}

料理の注文を受ける部分と、受けた注文を表示する部分を分離したので(意味的に)コードが少し読みやすくなっています

ビルダーパターン(Builder Pattern)を使ってみる

扱いたいデータの成り立ちをオブジェクト的に考えて、それをそのままプログラム上で表現するのがビルダーパターンです

  • ビルダーパターンは、順を追ってオブジェクトを組み立てていくことで複雑なオブジェクトをわかりやすく作り上げていく設計手法です
  • 設定ごとの膨大なサブクラスを用意したり、大量の条件を引数(コンストラクタ)で指定するクラスを作ったりしないで済みます
  • ただし、Kotlinではデフォルト引数と名前付き引数があるため、ビルダーパターンを意識せずに簡潔に書くこともできます

Order クラスのメソッドの戻り値を Order にする

メソッドで処理したインスタンスをそのまま返しちゃうんですね

  • Order クラスにある addItem() と addAll() の戻り値を Order型にして、要素の追加を行ったインスタンス自身を返すようにします
    • 変更前の addItem() と addAll() は値を返しません。値を返さない型をKotlinではUnit型といいます
  • インスタンス自身は this というキーワードで参照できます
fun addItem(newItem: Item): Order {
    itemList.add(newItem)
    return this
}

fun addAll(newItems: List<Item>): Order {
    itemList.addAll(newItems)
    return this
}

インスタンスの生成と、そのインスタンスへの設定を順を追って次々と行う

  • main() の中で Order() クラスから注文のインスタンスを生成してみます
  • addItem() メソッドがインスタンスを返すことで、チェーン状に連なったメソッドが左からつぎつぎと実行されていきます
  • ビルダーパターンによって、必要な条件だけを指定して目的とするオブジェクトを簡単につくることができます
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)

注文を受けて、そこに麺や野菜のトッピングが次々と追加されていく様子が、イメージそのままKotlinプログラムになっているかんじですね

うん、関数(メソッド)の戻り値を利用してチェーン状に次々と適用していくの。とっても便利なのでKotlinでどんどん使ってね

インスタンスの生成と、そのインスタンスへの設定を行ったうえで、リストへの代入までまとめて行う

次のように ordersList 変数に注文のインスタンスを直接追加すれば、記述はさらに簡単になります

ordersList.add(
    Order(5)
        .addItem(Noodles())
        .addItem(Noodles())
        .addItem(Vegetables("Spinach")))

すごく賢そう!

今回作成したプログラム

今回作成したKotlinプログラムです

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
    override fun toString(): String {
        return name
    }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()

    fun addItem(newItem: Item): Order {
        itemList.add(newItem)
        return this
    }

    fun addAll(newItems: List<Item>): Order {
        itemList.addAll(newItems)
        return this
    }

    fun print() {
        println("Order #${orderNumber}")
        var total = 0
        for (item in itemList) {
            println("${item}: $${item.price}")
            total += item.price
        }
        println("Total: $${total}")
    }
}

fun main() {
    val ordersList = mutableListOf<Order>()

    // Add an item to an order
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    // Add multiple items individually
    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    // Add a list of items at one time
    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    // Use builder pattern
    val order4 = Order(4)
        .addItem(Noodles())
        .addItem(Vegetables("Cabbage", "Onion"))
    ordersList.add(order4)

    // Create and add order directly
    ordersList.add(
        Order(5)
            .addItem(Noodles())
            .addItem(Noodles())
            .addItem(Vegetables("Spinach"))
    )

    // Print out each order
    for (order in ordersList) {
        order.print()
        println()
    }
}

実行するとこんな感じですね、お疲れ様でした

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25

参考