Bootcamp Kotlin untuk Programmer 4: Pemrograman berorientasi objek

Codelab ini adalah bagian dari kursus Bootcamp Kotlin untuk Programer. Anda akan mendapatkan manfaat maksimal dari kursus ini jika Anda mengerjakan codelab secara berurutan. Anda mungkin dapat membaca cepat beberapa bagian, bergantung pada pengetahuan Anda. Kursus ini ditujukan bagi programer yang menguasai bahasa berorientasi objek, dan ingin mempelajari Kotlin.

Pengantar

Dalam codelab ini, Anda akan membuat program Kotlin dan mempelajari class serta objek di Kotlin. Sebagian besar konten ini akan tidak asing lagi jika Anda mengetahui bahasa berorientasi objek lainnya, tetapi Kotlin memiliki beberapa perbedaan penting untuk mengurangi jumlah kode yang perlu Anda tulis. Anda juga mempelajari class abstrak dan delegasi antarmuka.

Daripada membuat aplikasi sampel tunggal, pelajaran dalam kursus ini didesain untuk membangun pengetahuan Anda, tetapi bersifat semi-mandiri, sehingga Anda dapat membaca cepat bagian yang sudah Anda pahami. Untuk menyatukannya, banyak contoh menggunakan tema akuarium. Dan jika Anda ingin melihat cerita lengkap akuarium, lihat kursus Udacity Kotlin Bootcamp for Programmers.

Yang harus sudah Anda ketahui

  • Dasar-dasar Kotlin, termasuk jenis, operator, dan loop
  • Sintaksis fungsi Kotlin
  • Dasar-dasar pemrograman berorientasi objek
  • Dasar-dasar IDE seperti IntelliJ IDEA atau Android Studio

Yang akan Anda pelajari

  • Cara membuat class dan mengakses properti di Kotlin
  • Cara membuat dan menggunakan konstruktor class di Kotlin
  • Cara membuat subclass dan cara kerja warisan
  • Tentang class abstrak, antarmuka, dan delegasi antarmuka
  • Cara membuat dan menggunakan class data
  • Cara menggunakan singleton, enum, dan class tertutup

Yang akan Anda lakukan

  • Membuat class dengan properti
  • Membuat konstruktor untuk class
  • Membuat subclass
  • Memeriksa contoh class dan antarmuka abstrak
  • Membuat class data sederhana
  • Mempelajari singleton, enum, dan class tertutup

Istilah pemrograman berikut seharusnya sudah tidak asing bagi Anda:

  • Class adalah blueprint untuk objek. Misalnya, class Aquarium adalah blueprint untuk membuat objek akuarium.
  • Objek adalah instance class; objek akuarium adalah satu Aquarium yang sebenarnya.
  • Properti adalah karakteristik class, seperti panjang, lebar, dan tinggi Aquarium.
  • Metode, juga disebut fungsi anggota, adalah fungsi class. Metode adalah hal yang dapat "lakukan" dengan objek. Misalnya, Anda dapat fillWithWater() objek Aquarium.
  • Antarmuka adalah spesifikasi yang dapat diterapkan oleh class. Misalnya, pembersihan bersifat umum untuk objek selain akuarium, dan pembersihan umumnya dilakukan dengan cara yang sama untuk objek yang berbeda. Jadi, Anda mungkin memiliki antarmuka bernama Clean yang menentukan metode clean(). Class Aquarium dapat mengimplementasikan antarmuka Clean untuk membersihkan akuarium dengan spons lembut.
  • Paket adalah cara untuk mengelompokkan kode terkait agar tetap teratur, atau untuk membuat library kode. Setelah paket dibuat, Anda dapat mengimpor konten paket ke dalam file lain dan menggunakan kembali kode dan class di dalamnya.

Dalam tugas ini, Anda akan membuat paket dan class baru dengan beberapa properti dan metode.

Langkah 1: Buat paket

Paket dapat membantu Anda menjaga kode tetap teratur.

  1. Di panel Project, di bagian project Hello Kotlin, klik kanan pada folder src.
  2. Pilih New > Package dan beri nama example.myapp.

Langkah 2: Buat class dengan properti

Class didefinisikan dengan kata kunci class, dan nama class berdasarkan konvensi dimulai dengan huruf kapital.

  1. Klik kanan paket example.myapp.
  2. Pilih New > Kotlin File / Class.
  3. Di bagian Kind, pilih Class, dan beri nama class Aquarium. IntelliJ IDEA menyertakan nama paket dalam file dan membuat class Aquarium kosong untuk Anda.
  4. Di dalam class Aquarium, tentukan dan inisialisasi properti var untuk lebar, tinggi, dan panjang (dalam sentimeter). Lakukan inisialisasi properti dengan nilai default.
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

Di balik layar, Kotlin secara otomatis membuat pengambil dan penyetel untuk properti yang Anda tentukan di class Aquarium, sehingga Anda dapat mengakses properti secara langsung, misalnya, myAquarium.length.

Langkah 3: Buat fungsi main()

Buat file baru bernama main.kt untuk menyimpan fungsi main().

  1. Di panel Project di sebelah kiri, klik kanan paket example.myapp.
  2. Pilih New > Kotlin File/Class.
  3. Pada dropdown Kind, biarkan pilihan sebagai File, dan beri nama file main.kt. IntelliJ IDEA menyertakan nama paket, tetapi tidak termasuk definisi class untuk file.
  4. Tentukan fungsi buildAquarium() dan di dalamnya, buat instance Aquarium. Untuk membuat instance, rujuk class seolah-olah itu adalah sebuah fungsi, Aquarium(). Tindakan ini akan memanggil konstruktor class dan membuat instance class Aquarium, mirip dengan penggunaan new dalam bahasa lain.
  5. Tentukan fungsi main() dan panggil buildAquarium().
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

Langkah 4: Tambahkan metode

  1. Di class Aquarium, tambahkan metode untuk mencetak properti dimensi akuarium.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. Di main.kt, di buildAquarium(), panggil metode printSize() di myAquarium.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. Jalankan program dengan mengklik segitiga hijau di samping fungsi main(). Amati hasilnya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. Di buildAquarium(), tambahkan kode untuk menetapkan tinggi ke 60 dan mencetak properti dimensi yang diubah.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. Jalankan program Anda dan amati outputnya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

Dalam tugas ini, Anda akan membuat konstruktor untuk class dan terus menggunakan properti.

Langkah 1: Buat konstruktor

Pada langkah ini, Anda menambahkan konstruktor ke class Aquarium yang Anda buat di tugas pertama. Pada contoh sebelumnya, setiap instance Aquarium dibuat dengan dimensi yang sama. Anda dapat mengubah dimensi setelah dibuat dengan menetapkan properti tersebut, tetapi akan lebih mudah untuk membuat ukuran yang tepat untuk memulai.

Dalam beberapa bahasa pemrograman, konstruktor didefinisikan dengan membuat metode dalam class yang memiliki nama yang sama dengan class. Di Kotlin, Anda menentukan konstruktor secara langsung dalam deklarasi class itu sendiri, menentukan parameter dalam tanda kurung seolah-olah class tersebut adalah sebuah metode. Seperti fungsi di Kotlin, parameter tersebut dapat menyertakan nilai default.

  1. Di class Aquarium yang Anda buat sebelumnya, ubah definisi class untuk menyertakan tiga parameter konstruktor dengan nilai default untuk length, width dan height, dan tetapkan ke properti yang sesuai.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. Cara Kotlin yang lebih ringkas adalah menentukan properti secara langsung dengan konstruktor menggunakan var atau val, dan Kotlin juga membuat pengambil dan penyetel secara otomatis. Kemudian Anda dapat menghapus definisi properti dalam isi class.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. Saat membuat objek Aquarium dengan konstruktor tersebut, Anda dapat menentukan tanpa argumen dan mendapatkan nilai default, atau menentukan beberapa dari nilai tersebut, atau menentukan semuanya dan membuat Aquarium yang sepenuhnya kustom. Pada fungsi buildAquarium(), coba berbagai cara membuat objek Aquarium menggunakan parameter bernama.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. Jalankan program dan amati outputnya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

Perhatikan bahwa Anda tidak harus membebani konstruktor dan menulis versi yang berbeda untuk setiap kasus ini (ditambah beberapa lagi untuk kombinasi lainnya). Kotlin membuat apa yang diperlukan dari nilai default dan parameter bernama.

Langkah 2: Tambahkan blok init

Contoh konstruktor di atas cukup mendeklarasikan properti dan menetapkan nilai ekspresi ke properti tersebut. Jika konstruktor Anda memerlukan lebih banyak kode inisialisasi, konstruktor dapat ditempatkan dalam satu atau beberapa blok init. Pada langkah ini, Anda menambahkan beberapa blok init ke class Aquarium.

  1. Di class Aquarium, tambahkan blok init untuk mencetak bahwa objek sedang diinisialisasi, dan blok kedua untuk mencetak volume dalam liter.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. Jalankan program dan amati outputnya.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

Perhatikan bahwa blok init dieksekusi dalam urutan kemunculannya dalam definisi class, dan semuanya dijalankan saat konstruktor dipanggil.

Langkah 3: Pelajari konstruktor sekunder

Pada langkah ini, Anda akan mempelajari konstruktor sekunder dan menambahkannya ke class Anda. Selain konstruktor utama, yang dapat memiliki satu atau beberapa blok init, class Kotlin juga dapat memiliki satu atau beberapa konstruktor sekunder untuk memungkinkan kelebihan beban konstruktor, yaitu konstruktor dengan argumen yang berbeda.

  1. Di class Aquarium, tambahkan konstruktor sekunder yang menggunakan sejumlah ikan sebagai argumennya, menggunakan kata kunci constructor. Membuat properti tangki val untuk volume akuarium yang dihitung dalam liter berdasarkan jumlah ikan. Asumsikan 2 liter (2.000 cm^3) air per ikan, ditambah sedikit ruang ekstra agar air tidak tumpah.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. Di dalam konstruktor sekunder, pertahankan panjang dan lebar (yang disetel dalam konstruktor utama) tetap sama, dan hitung tinggi yang diperlukan untuk membuat volume sesuai dengan ukuran tangki.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. Dalam fungsi buildAquarium(), tambahkan panggilan untuk membuat Aquarium menggunakan konstruktor sekunder baru. Cetak ukuran dan volume.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. Jalankan program Anda dan amati outputnya.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Perhatikan bahwa volume dicetak dua kali, sekali oleh blok init di konstruktor utama sebelum konstruktor sekunder dijalankan, dan sekali oleh kode dalam buildAquarium().

Anda juga dapat menyertakan kata kunci constructor di konstruktor utama, tetapi dalam banyak kasus, kata kunci tersebut tidak diperlukan.

Langkah 4: Tambahkan pengambil properti baru

Pada langkah ini, Anda menambahkan pengambil properti eksplisit. Kotlin secara otomatis menentukan pengambil dan penyetel saat Anda menentukan properti, tetapi terkadang nilai untuk properti perlu disesuaikan atau dihitung. Misalnya, di atas, Anda mencetak volume Aquarium. Anda dapat membuat volume tersedia sebagai properti dengan menentukan variabel dan pengambil untuk properti tersebut. Karena volume perlu dihitung, pengambil harus menampilkan nilai yang dihitung, yang dapat Anda lakukan dengan fungsi satu baris.

  1. Di class Aquarium, tentukan properti Int yang disebut volume, dan tentukan metode get() yang menghitung volume di baris berikutnya.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. Hapus blok init yang mencetak volume.
  2. Hapus kode dalam buildAquarium() yang mencetak volume.
  3. Pada metode printSize(), tambahkan baris untuk mencetak volume.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. Jalankan program Anda dan amati outputnya.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Dimensi dan volume sama seperti sebelumnya, tetapi volume hanya dicetak sekali setelah objek diinisialisasi sepenuhnya oleh konstruktor utama dan konstruktor sekunder.

Langkah 5: Tambahkan penyetel properti

Pada langkah ini, Anda membuat penyetel properti baru untuk volume.

  1. Di class Aquarium, ubah volume menjadi var sehingga dapat disetel lebih dari sekali.
  2. Tambahkan penyetel untuk properti volume dengan menambahkan metode set() di bawah pengambil, yang menghitung ulang tinggi berdasarkan jumlah air yang diberikan. Berdasarkan konvensi, nama parameter penyetel adalah value, tetapi Anda dapat mengubahnya jika diinginkan.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. Di buildAquarium(), tambahkan kode untuk menyetel volume Akuarium ke 70 liter. Cetak ukuran baru.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. Jalankan program Anda lagi dan amati tinggi dan volume yang berubah.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

Sejauh ini belum ada pengubah visibilitas, seperti public atau private, dalam kode. Itu karena secara default, semua yang ada di Kotlin bersifat publik, yang berarti bahwa semuanya dapat diakses di mana saja, termasuk class, metode, properti, dan variabel anggota.

Di Kotlin, class, objek, antarmuka, konstruktor, fungsi, properti, dan penyetelnya dapat memiliki pengubah visibilitas:

  • public berarti terlihat di luar class. Semuanya merupakan publik secara default, termasuk variabel dan metode class.
  • internal berarti elemen hanya akan terlihat dalam modul tersebut. Modul adalah sekumpulan file Kotlin yang dikompilasi bersama, misalnya, library atau aplikasi.
  • private berarti hanya akan terlihat di class tersebut (atau file sumber jika Anda bekerja dengan fungsi).
  • protected sama dengan private, tetapi juga akan terlihat di subclass mana pun.

Lihat Pengubah Visibilitas dalam dokumentasi Kotlin untuk informasi selengkapnya.

Variabel anggota

Properti dalam class atau variabel anggota adalah public secara default. Jika Anda menentukannya dengan var, parameter tersebut dapat diubah, yaitu dapat dibaca dan ditulis. Jika Anda menentukannya dengan val, variabel tersebut bersifat hanya baca setelah inisialisasi.

Jika Anda menginginkan properti yang dapat dibaca atau ditulis oleh kode Anda, tetapi kode luar hanya dapat dibaca, Anda dapat membiarkan properti tersebut dan pengambilnya sebagai publik dan mendeklarasikan penyetel sebagai pribadi, seperti yang ditampilkan di bawah ini.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

Dalam tugas ini, Anda akan mempelajari cara kerja subclass dan turunan di Kotlin. Keduanya mirip dengan bahasa yang telah Anda lihat dalam bahasa lain, tetapi ada beberapa perbedaan.

Di Kotlin, secara default, class tidak dapat dibuatkan subclass-nya. Demikian pula, properti dan variabel anggota tidak dapat diganti oleh subclass (meskipun dapat diakses).

Anda harus menandai class sebagai open agar dapat dibuatkan subclass-nya. Demikian pula, Anda harus menandai properti dan variabel anggota sebagai open, untuk menggantinya di subclass. Kata kunci open diperlukan untuk mencegah kebocoran detail implementasi secara tidak sengaja sebagai bagian dari antarmuka class.

Langkah 1: Buat kelas Akuarium terbuka

Pada langkah ini, Anda akan membuat class Aquarium open, sehingga Anda dapat menggantinya di langkah berikutnya.

  1. Tandai class Aquarium dan semua propertinya dengan kata kunci open.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. Tambahkan properti shape terbuka dengan nilai "rectangle".
   open val shape = "rectangle"
  1. Tambahkan properti water terbuka dengan pengambil yang menampilkan 90% volume Aquarium.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. Tambahkan kode ke metode printSize() untuk mencetak bentuk, dan jumlah air sebagai persentase volume.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. Di buildAquarium(), ubah kode untuk membuat Aquarium dengan width = 25, length = 25, dan height = 40.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. Jalankan program Anda dan amati output baru.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

Langkah 2: Buat subclass

  1. Buat subclass Aquarium yang disebut TowerTank, yang menerapkan tangki silinder bulat, bukan tangki persegi panjang. Anda dapat menambahkan TowerTank di bawah Aquarium, karena Anda dapat menambahkan class lain dalam file yang sama dengan class Aquarium.
  2. Di TowerTank, ganti properti height, yang ditentukan dalam konstruktor. Untuk mengganti properti, gunakan kata kunci override di subclass.
  1. Membuat konstruktor untuk TowerTank mengambil diameter. Gunakan diameter untuk length dan width saat memanggil konstruktor di superclass Aquarium.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. Ganti properti volume untuk menghitung silinder. Formula untuk silinder adalah pi yang dikalikan jari-jari pangkat dua kali tinggi. Anda perlu mengimpor konstanta PI dari java.lang.Math.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. Di TowerTank, ganti properti water menjadi 80% dari volume.
override var water = volume * 0.8
  1. Ganti shape menjadi "cylinder".
override val shape = "cylinder"
  1. Class TowerTank akhir akan terlihat seperti kode di bawah ini.

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. Di buildAquarium(), buat TowerTank dengan diameter 25 cm dan tinggi 45 cm. Cetak ukurannya.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. Jalankan program Anda dan amati outputnya.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

Terkadang Anda ingin menentukan perilaku atau properti umum untuk dibagikan di antara beberapa class terkait. Kotlin menawarkan dua cara untuk melakukannya, yaitu antarmuka dan class abstrak. Dalam tugas ini, Anda membuat class AquariumFish abstrak untuk properti yang umum untuk semua ikan. Anda membuat antarmuka FishAction untuk menentukan perilaku yang umum pada semua ikan.

  • Baik class abstrak maupun antarmuka tidak dapat dibuat instance-nya sendiri, yang berarti Anda tidak dapat membuat objek jenis tersebut secara langsung.
  • Class abstrak memiliki konstruktor.
  • Antarmuka tidak boleh memiliki logika konstruktor atau menyimpan status apa pun.

Langkah 1. Membuat class abstrak

  1. Pada example.myapp, buat file baru, AquariumFish.kt.
  2. Buat class, juga disebut AquariumFish, dan tandai dengan abstract.
  3. Tambahkan satu properti String, color, dan tandai dengan abstract.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. Buat dua subclass dari AquariumFish, Shark, dan Plecostomus.
  2. Karena color bersifat abstrak, subclass harus menerapkannya. Jadikan Shark berwarna abu-abu dan Plecostomus emas.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. Di main.kt, buat fungsi makeFish() untuk menguji class Anda. Buat instance Shark dan Plecostomus, lalu cetak warna masing-masing.
  2. Hapus kode pengujian Anda sebelumnya di main() dan tambahkan panggilan ke makeFish(). Kodenya akan terlihat seperti berikut ini.

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. Jalankan program Anda dan amati outputnya.
⇒ Shark: gray 
Plecostomus: gold

Diagram berikut mewakili class Shark dan class Plecostomus, yang membuat subclass untuk class abstrak, AquariumFish.

Diagram yang menunjukkan class abstrak, AquariumFish, dan dua subclass, Shark dan Plecostumus.

Langkah 2. Membuat antarmuka

  1. Di AquariumFish.kt, buat antarmuka yang disebut FishAction dengan metode eat().
interface FishAction  {
    fun eat()
}
  1. Tambahkan FishAction ke setiap subclass, dan implementasikan eat() agar dapat mencetak fungsi ikan.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Dalam fungsi makeFish(), minta setiap ikan yang Anda buat akan makan sesuatu dengan memanggil eat().
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. Jalankan program Anda dan amati outputnya.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

Diagram berikut mewakili class Shark dan class Plecostomus, yang keduanya terdiri dari dan mengimplementasikan antarmuka FishAction.

Kapan class abstrak versus antarmuka digunakan

Contoh di atas sederhana, tetapi jika Anda memiliki banyak class yang saling terkait, class dan antarmuka abstrak dapat membantu membuat desain Anda lebih rapi, lebih teratur, dan lebih mudah dikelola.

Seperti disebutkan di atas, class abstrak dapat memiliki konstruktor, dan antarmuka tidak bisa, tetapi sangat mirip. Jadi, kapan sebaiknya Anda menggunakan keduanya?

Ketika Anda menggunakan antarmuka untuk menulis class, fungsionalitas class diperluas melalui instance class yang dikandungnya. Komposisi cenderung membuat kode lebih mudah digunakan kembali dan memberikan alasan daripada pewarisan dari class abstrak. Selain itu, Anda dapat menggunakan beberapa antarmuka di class, tetapi Anda hanya dapat membuat subclass dari satu class abstrak.

Komposisi sering kali menghasilkan enkapsulasi yang lebih baik, penggabungan (interdependensi) yang lebih rendah, antarmuka yang lebih rapi, dan kode yang lebih bermanfaat. Untuk alasan ini, penggunaan komposisi dengan antarmuka adalah desain yang disukai. Di sisi lain, pewarisan dari class abstrak cenderung lebih cocok untuk beberapa masalah. Jadi, Anda harus memilih komposisi, tetapi ketika pewarisan masuk akal, Kotlin juga memungkinkan Anda melakukannya!

  • Gunakan antarmuka jika Anda memiliki banyak metode dan satu atau dua penerapan default, misalnya seperti pada AquariumAction di bawah.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • Gunakan kelas abstrak kapan pun Anda tidak dapat menyelesaikan kelas. Misalnya, dengan kembali ke class AquariumFish, Anda dapat membuat semua AquariumFish mengimplementasikan FishAction, dan menyediakan implementasi default untuk eat sekaligus membiarkan color abstrak, karena tidak ada warna default untuk ikan.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

Tugas sebelumnya memperkenalkan class abstrak, antarmuka, dan ide komposisi. Delegasi antarmuka adalah teknik lanjutan dengan metode antarmuka yang diterapkan oleh objek helper (atau delegasi), yang kemudian digunakan oleh class. Teknik ini dapat berguna saat Anda menggunakan antarmuka dalam serangkaian class yang tidak terkait: Anda menambahkan fungsi antarmuka yang diperlukan ke class helper yang terpisah, dan setiap class menggunakan instance class helper untuk menerapkan fungsi.

Dalam tugas ini, Anda menggunakan delegasi antarmuka untuk menambahkan fungsi ke class.

Langkah 1: Buat antarmuka baru

  1. Di AquariumFish.kt, hapus class AquariumFish. Daripada mewarisi dari class AquariumFish, Plecostomus dan Shark akan mengimplementasikan antarmuka untuk tindakan ikan dan warnanya.
  2. Buat antarmuka baru, FishColor, yang menentukan warna sebagai string.
interface FishColor {
    val color: String
}
  1. Mengubah Plecostomus untuk mengimplementasikan dua antarmuka, FishAction, dan FishColor. Anda perlu mengganti color dari FishColor dan eat() dari FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Ubah class Shark Anda untuk menerapkan juga dua antarmuka, FishAction dan FishColor, bukan mewarisi dari AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. Kode yang sudah selesai akan terlihat seperti ini:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

Langkah 2: Buat class singleton

Selanjutnya, Anda akan mengimplementasikan penyiapan untuk bagian delegasi dengan membuat class helper yang mengimplementasikan FishColor. Anda akan membuat class dasar bernama GoldColor yang mengimplementasikan FishColor—yang hanya mengatakan bahwa warnanya adalah emas.

Tidak masuk akal untuk membuat beberapa instance GoldColor, karena semuanya melakukan hal yang sama persis. Jadi, Kotlin memungkinkan Anda mendeklarasikan class tempat Anda hanya dapat membuat satu instance dengan menggunakan kata kunci object, bukan class. Kotlin akan membuat satu instance tersebut, dan instance tersebut direferensikan oleh nama class. Lalu semua objek lainnya dapat menggunakan instance ini saja, tidak ada cara untuk membuat instance class ini. Jika Anda sudah terbiasa dengan pola singleton, ini adalah cara Anda menerapkan singleton di Kotlin.

  1. Di AquariumFish.kt, buat objek untuk GoldColor. Mengganti warna.
object GoldColor : FishColor {
   override val color = "gold"
}

Langkah 3: Tambahkan delegasi antarmuka untuk FishColor

Sekarang Anda siap untuk menggunakan delegasi antarmuka.

  1. Di AquariumFish.kt, hapus penggantian color dari Plecostomus.
  2. Ubah class Plecostomus untuk mendapatkan warnanya dari GoldColor. Untuk melakukannya, tambahkan by GoldColor ke deklarasi class, lalu buat delegasi. Artinya, daripada menerapkan FishColor, gunakan implementasi yang disediakan oleh GoldColor. Jadi, setiap kali diakses, color akan didelegasikan ke GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

Dengan kelas apa adanya, semua Plecos akan berwarna emas, tetapi ikan ini sebenarnya datang dalam berbagai warna. Anda dapat mengatasinya dengan menambahkan parameter konstruktor untuk warna dengan GoldColor sebagai warna default untuk Plecostomus.

  1. Ubah class Plecostomus untuk menerima fishColor di konstruktornya, dan setel default-nya ke GoldColor. Ubah delegasi dari by GoldColor menjadi by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

Langkah 4: Tambahkan delegasi antarmuka untuk FishAction

Dengan cara yang sama, Anda dapat menggunakan delegasi antarmuka untuk FishAction.

  1. Dalam AquariumFish.kt, buat class PrintingFishAction yang mengimplementasikan FishAction, yang mengambil String, food, lalu mencetak apa yang dimakan ikan.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. Di class Plecostomus, hapus fungsi penggantian eat(), karena Anda akan menggantinya dengan delegasi.
  2. Pada deklarasi Plecostomus, delegasikan FishAction ke PrintingFishAction, yang meneruskan "eat algae".
  3. Dengan semua delegasi tersebut, tidak ada kode dalam isi class Plecostomus, jadi hapus {}, karena semua penggantian ditangani oleh delegasi antarmuka
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Diagram berikut mewakili class Shark dan Plecostomus, keduanya terdiri dari antarmuka PrintingFishAction dan FishColor, tetapi mendelegasikan implementasi untuknya.

Delegasi antarmuka sangat kuat, dan secara umum Anda harus mempertimbangkan cara menggunakannya setiap kali menggunakan class abstrak dalam bahasa lain. Alat ini memungkinkan Anda menggunakan komposisi untuk memasukkan perilaku, tanpa memerlukan banyak subclass, yang masing-masing memiliki cara berbeda.

Class data mirip dengan struct di beberapa bahasa lain—class ini utamanya ada untuk menyimpan beberapa data—tetapi objek class data masih menjadi objek. Objek class data Kotlin memiliki beberapa manfaat tambahan, seperti utilitas untuk mencetak dan menyalin. Dalam tugas ini, Anda akan membuat class data sederhana dan mempelajari dukungan yang disediakan Kotlin untuk class data.

Langkah 1: Buat class data

  1. Tambahkan paket baru decor di bagian paket example.myapp untuk menyimpan kode baru. Klik kanan example.myapp di panel Project, lalu pilih File > New > Package.
  2. Dalam paket, buat class baru bernama Decoration.
package example.myapp.decor

class Decoration {
}
  1. Untuk membuat Decoration class data, awali deklarasi class dengan kata kunci data.
  2. Tambahkan properti String yang disebut rocks untuk memberikan data ke class.
data class Decoration(val rocks: String) {
}
  1. Di dalam file, di luar class, tambahkan fungsi makeDecorations() untuk membuat dan mencetak instance Decoration dengan "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. Tambahkan fungsi main() untuk memanggil makeDecorations(), dan jalankan program Anda. Perhatikan output logis yang dibuat karena ini adalah class data.
⇒ Decoration(rocks=granite)
  1. Di makeDecorations(), buat instance dua objek Decoration lagi yang keduanya merupakan "slate" lalu mencetaknya.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. Di makeDecorations(), tambahkan pernyataan cetak yang mencetak hasil perbandingan decoration1 dengan decoration2, dan pernyataan kedua yang membandingkan decoration3 dengan decoration2. Gunakan metode equals() yang disediakan oleh class data.
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. Jalankan kode.
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

Langkah 2. Menggunakan destrukturisasi

Untuk mendapatkan properti objek data dan menetapkannya ke variabel, Anda dapat menetapkannya satu per satu, seperti ini.

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

Sebagai gantinya, Anda dapat membuat variabel, satu untuk setiap properti, dan menetapkan objek data ke grup variabel. Kotlin menempatkan nilai properti di setiap variabel.

val (rock, wood, diver) = decoration

Ini disebut destrukturisasi dan merupakan singkatan yang berguna. Jumlah variabel harus cocok dengan jumlah properti, dan variabel ditetapkan sesuai urutan deklarasinya di class tersebut. Berikut adalah contoh lengkap yang dapat Anda coba di Decoration.kt.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

Jika tidak memerlukan satu atau beberapa properti, Anda dapat melewatinya menggunakan _, bukan nama variabel, seperti yang ditunjukkan pada kode di bawah ini.

    val (rock, _, diver) = d5

Dalam tugas ini, Anda akan mempelajari beberapa class dengan tujuan khusus di Kotlin, termasuk yang berikut:

  • Class singleton
  • Enumerasi
  • Kelas tertutup

Langkah 1: Tarik kembali class singleton

Ingat kembali contoh sebelumnya dengan class GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

Karena setiap instance GoldColor melakukan hal yang sama, instance tersebut dideklarasikan sebagai object, bukan sebagai class untuk menjadikannya singleton. Hanya boleh ada satu instance.

Langkah 2: Buat enum

Kotlin juga mendukung enumerasi, yang memungkinkan Anda menghitung sesuatu dan merujuknya berdasarkan nama, sama seperti dalam bahasa lain. Deklarasikan enum dengan mengawali awalan dengan kata kunci enum. Deklarasi enum dasar hanya memerlukan daftar nama, namun Anda juga dapat menentukan satu atau beberapa kolom yang terkait dengan setiap nama.

  1. Dalam Decoration.kt, coba contoh enum.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Enum sedikit mirip dengan singleton—hanya boleh ada satu, dan hanya satu dari setiap nilai dalam enumerasi. Misalnya, hanya boleh ada satu Color.RED, satu Color.GREEN, dan satu Color.BLUE. Dalam contoh ini, nilai RGB ditetapkan ke properti rgb untuk mewakili komponen warna. Anda juga bisa mendapatkan nilai ordinal enum menggunakan properti ordinal, dan namanya menggunakan properti name.

  1. Coba contoh lain enum.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

Langkah 3: Buat class tertutup

Class tertutup adalah class yang dapat dibuat subclass, tetapi hanya di dalam file tempatnya dideklarasikan. Jika Anda mencoba membuat subclass untuk class di file yang berbeda, Anda akan mendapatkan error.

Karena class dan subclass berada di file yang sama, Kotlin akan mengetahui semua subclass secara statis. Artinya, pada waktu kompilasi, compiler melihat semua class dan subclass dan mengetahui bahwa ini adalah semuanya, sehingga compiler dapat melakukan pemeriksaan tambahan untuk Anda.

  1. Di AquariumFish.kt, cobalah contoh class tertutup, agar sesuai dengan tema akuatik.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

Class Seal tidak dapat dibuatkan subclass-nya dalam file lain. Jika ingin menambahkan lebih banyak jenis Seal, Anda harus menambahkannya dalam file yang sama. Oleh karena itu, class tertutup menjadi cara yang aman untuk mewakili jenis tertentu. Misalnya, class tertutup cocok untuk menampilkan keberhasilan atau error dari API jaringan.

Pelajaran ini membahas banyak hal. Meskipun banyak dari elemen tersebut seharusnya sudah dikenal dari bahasa pemrograman berorientasi objek lainnya, Kotlin menambahkan beberapa fitur untuk menjaga kode tetap ringkas dan mudah dibaca.

Class dan konstruktor

  • Tentukan class di Kotlin menggunakan class.
  • Kotlin secara otomatis membuat penyetel dan pengambil untuk properti.
  • Menentukan konstruktor utama secara langsung dalam definisi class. Contoh:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • Jika konstruktor utama memerlukan kode tambahan, tulis kode tersebut dalam satu atau beberapa blok init.
  • Class dapat menentukan satu atau beberapa konstruktor sekunder menggunakan constructor, tetapi gaya Kotlin adalah menggunakan fungsi factory sebagai gantinya.

Pengubah visibilitas dan subclass

  • Semua class dan fungsi di Kotlin secara default ditetapkan ke public, tetapi Anda dapat menggunakan pengubah untuk mengubah visibilitas ke internal, private, atau protected.
  • Untuk membuat subclass, class induk harus ditandai sebagai open.
  • Untuk mengganti metode dan properti di subclass, metode dan properti harus ditandai open di class induk.
  • Class tertutup hanya dapat dibuat subclass di file yang sama tempatnya ditentukan. Buat class tertutup dengan mengawali deklarasi dengan sealed.

Class data, singleton, dan enum

  • Buat class data dengan menambahkan awalan ke data di deklarasi.
  • Destrukturisasi adalah singkatan untuk menetapkan properti objek data ke variabel terpisah.
  • Buat class singleton dengan menggunakan object bukan class.
  • Tentukan enum menggunakan enum class.

Class, antarmuka, dan delegasi abstrak

  • Class dan antarmuka abstrak adalah dua cara untuk berbagi perilaku umum di antara class.
  • Class abstrak menentukan properti dan perilaku, tetapi menerapkan implementasinya ke subclass.
  • Antarmuka menentukan perilaku, dan dapat menyediakan implementasi default untuk sebagian atau semua perilaku.
  • Ketika Anda menggunakan antarmuka untuk menulis class, fungsionalitas class diperluas melalui instance class yang dikandungnya.
  • Delegasi antarmuka menggunakan komposisi, tetapi juga mendelegasikan implementasi ke class antarmuka.
  • Komposisi adalah cara yang efektif untuk menambahkan fungsi ke class menggunakan delegasi antarmuka. Dalam komposisi umum lebih disarankan, tetapi pewarisan dari class abstrak lebih cocok untuk beberapa masalah.

Dokumentasi Kotlin

Jika Anda menginginkan informasi lebih lanjut tentang setiap topik dalam kursus ini, atau jika mengalami kesulitan, https://kotlinlang.org adalah langkah awal yang terbaik.

Tutorial Kotlin

Situs https://try.kotlinlang.org menyertakan berbagai tutorial yang disebut Kotlin Koans, penafsir berbasis web, dan kumpulan lengkap dokumentasi referensi dengan contoh.

Kursus Udacity

Untuk melihat kursus Udacity tentang topik ini, lihat Bootcamp Kotlin untuk Pemrogram

IntelliJ IDEA

Dokumentasi untuk IntelliJ IDEA dapat ditemukan di situs JetBrains.

Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Terserah instruktur untuk melakukan hal berikut:

  • Tugaskan pekerjaan rumah jika diperlukan.
  • Berkomunikasi dengan siswa cara mengirimkan tugas pekerjaan rumah.
  • Beri nilai tugas pekerjaan rumah.

Instruktur dapat menggunakan saran ini sesedikit atau sebanyak yang mereka inginkan, dan harus bebas memberikan pekerjaan rumah lain yang dirasa sesuai.

Jika Anda mengerjakan codelab ini sendiri, silakan gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.

Jawab pertanyaan berikut

Pertanyaan 1

Class memiliki metode khusus yang berfungsi sebagai blueprint untuk membuat objek dari class tersebut. Metode apa yang digunakan?

▢ Builder

▢ Instance

▢ Konstruktor

▢ Cetak biru

Pertanyaan 2

Manakah dari pernyataan tentang antarmuka dan class abstrak berikut yang TIDAK benar?

▢ Class abstrak dapat memiliki konstruktor.

▢ Antarmuka tidak boleh memiliki konstruktor.

▢ Antarmuka dan class abstrak dapat dibuat instance secara langsung.

▢ Properti abstrak harus diimplementasikan oleh subclass dari class abstrak.

Pertanyaan 3

Manakah dari berikut ini yang BUKAN merupakan pengubah visibilitas Kotlin untuk properti, metode, dll.?

internal

nosubclass

protected

private

Pertanyaan 4

Perhatikan class data ini:
data class Fish(val name: String, val species:String, val colors:String)
Manakah dari kode berikut yang BUKAN kode yang valid untuk membuat dan membatalkan struktur objek Fish?

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

Pertanyaan 5

Misalnya Anda memiliki kebun binatang dengan banyak hewan yang harus dipelihara. Manakah dari berikut ini yang BUKAN merupakan bagian dari penerapan perawatan?

interface untuk berbagai jenis makanan yang dimakan hewan.

▢ Class abstract Caretaker tempat Anda dapat membuat berbagai jenis pengasuh.

interface untuk menyediakan air bersih bagi hewan.

▢ Class data untuk entri dalam jadwal pemberian makan.

Lanjutkan ke pelajaran berikutnya: 5.1 Ekstensi

Untuk ringkasan kursus, termasuk link ke codelab lainnya, lihat "Kotlin Bootcamp for Programmers: Selamat datang di kursus."