Menggunakan class dan objek di Kotlin

1. Sebelum memulai

Codelab ini mengajarkan cara menggunakan class dan objek di Kotlin.

Class menyediakan cetak biru tempat objek dapat dibuat. Objek adalah instance dari class yang terdiri dari data yang spesifik untuk objek tersebut. Anda dapat menggunakan objek atau instance class secara bergantian.

Sebagai analogi, bayangkan Anda akan membangun rumah. Class serupa dengan rencana desain arsitek, juga dikenal sebagai cetak biru. Cetak biru bukanlah rumah, melainkan petunjuk cara membangun rumah. Rumah adalah hal yang sebenarnya, atau objek, yang dibuat berdasarkan cetak biru.

Sama seperti cetak biru rumah yang mendeskripsikan beberapa ruangan dan setiap ruangan memiliki desain dan tujuan masing-masing, setiap class memiliki desain dan tujuannya sendiri. Untuk mengetahui cara mendesain class, Anda harus memahami pemrograman berorientasi objek (OOP), sebuah framework yang mengajarkan Anda untuk memasukkan data, logika, dan perilaku dalam objek.

OOP membantu Anda menyederhanakan masalah dunia nyata yang kompleks menjadi objek yang lebih kecil. Ada empat konsep dasar OOP yang masing-masing akan Anda pelajari lebih lanjut di codelab ini:

  • Enkapsulasi. Menggabungkan properti dan metode terkait yang melakukan tindakan pada properti tersebut dalam class. Misalnya, perhatikan ponsel Anda. Ponsel itu mencakup kamera, layar, kartu memori, dan beberapa komponen hardware dan software lainnya. Anda tidak perlu khawatir tentang cara komponen disambungkan secara internal.
  • Abstraksi. Ekstensi untuk enkapsulasi. Idenya adalah sebisa mungkin menyembunyikan logika implementasi internal. Misalnya, untuk mengambil foto dengan ponsel, yang perlu Anda lakukan adalah membuka aplikasi kamera, mengarahkan ponsel ke adegan yang ingin Anda ambil, dan mengklik tombol untuk mengambil foto. Anda tidak perlu mengetahui cara aplikasi kamera dibuat atau cara kerja hardware kamera yang sebenarnya. Singkatnya, mekanisme internal pada aplikasi kamera dan cara kamera seluler mengambil foto diabstraksi untuk memungkinkan Anda melakukan tugas yang penting.
  • Pewarisan. Memungkinkan Anda membangun class berdasarkan karakteristik dan perilaku class lain dengan menetapkan hubungan induk-turunan. Misalnya, ada berbagai produsen yang memproduksi berbagai perangkat seluler yang menjalankan Android OS, tetapi UI untuk setiap perangkat berbeda. Dengan kata lain, produsen mewarisi fitur Android OS dan membuat penyesuaian di atasnya.
  • Polimorfisme. Kata tersebut adalah adaptasi dari akar kata Yunani poly- yang berarti banyak, dan -morphism yang berarti bentuk. Polimorfisme adalah kemampuan untuk menggunakan berbagai objek dengan satu cara yang sama. Misalnya, saat Anda menghubungkan speaker Bluetooth ke ponsel, ponsel itu hanya perlu mengetahui bahwa ada perangkat yang dapat memutar audio melalui Bluetooth. Namun, ada berbagai speaker Bluetooth yang dapat Anda pilih, dan ponsel Anda tidak perlu mengetahui cara bekerja dengan setiap speaker tersebut secara khusus.

Terakhir, Anda akan mempelajari delegasi properti yang memberikan kode yang dapat digunakan kembali untuk mengelola nilai properti dengan sintaksis yang ringkas. Dalam codelab ini, Anda akan mempelajari konsep tersebut saat membangun struktur class untuk aplikasi smart-home.

Prasyarat

  • Cara membuka, mengedit, dan menjalankan kode di Kotlin Playground.
  • Pengetahuan tentang dasar-dasar pemrograman Kotlin, termasuk variabel, fungsi, serta fungsi println() dan main()

Yang akan Anda pelajari

  • Ringkasan tentang OOP.
  • Pengertian class.
  • Cara menentukan class dengan konstruktor, fungsi, dan properti.
  • Cara membuat instance objek.
  • Pengertian pewarisan.
  • Perbedaan antara hubungan IS-A dan HAS-A.
  • Cara mengganti properti dan fungsi.
  • Pengertian pengubah visibilitas.
  • Pengertian delegasi dan cara menggunakan delegasi by.

Yang akan Anda bangun

  • Struktur class smart-home.
  • Class yang merepresentasikan perangkat smart, seperti smart TV dan lampu smart.

Yang akan Anda butuhkan

  • Komputer dengan akses internet dan browser web

2. Menentukan class

Saat menentukan class, Anda menentukan properti dan metode yang harus dimiliki semua objek class tersebut.

Definisi class dimulai dengan kata kunci class, diikuti dengan nama dan sepasang tanda kurung kurawal. Bagian dari sintaksis sebelum kurung kurawal buka juga disebut sebagai header class. Dalam tanda kurung kurawal, Anda dapat menentukan properti dan fungsi untuk class. Anda akan segera mempelajari properti dan fungsi. Anda dapat melihat sintaksis definisi class dalam diagram ini:

Dimulai dengan kata kunci class, diikuti dengan nama serta sepasang kurung kurawal buka dan tutup. Kurung kurawal berisi isi class yang menjelaskan cetakan birunya.

Berikut adalah konvensi penamaan yang direkomendasikan untuk class:

  • Anda dapat memilih nama class apa pun yang Anda inginkan, tetapi jangan gunakan kata kunci Kotlin sebagai nama class, seperti kata kunci fun.
  • Nama class ditulis dalam PascalCase sehingga setiap kata dimulai dengan huruf kapital dan tidak ada spasi di antara kata tersebut. Misalnya, pada kata PerangkatSmart, huruf pertama setiap kata ditulis dengan huruf kapital dan tidak ada spasi di antara kata tersebut.

Class terdiri dari tiga bagian utama:

  • Properti. Variabel yang menentukan atribut objek class.
  • Metode. Fungsi yang berisi tindakan dan perilaku class.
  • Konstruktor. Fungsi anggota khusus yang membuat instance class di seluruh program yang menentukannya.

Ini bukan pertama kalinya Anda bekerja dengan class. Pada codelab sebelumnya, Anda telah mempelajari jenis data, seperti jenis data Int, Float, String, dan Double. Jenis data ini ditentukan sebagai class di Kotlin. Saat Anda menentukan variabel seperti yang ditunjukkan dalam cuplikan kode ini, Anda akan membuat objek dari class Int, yang instance-nya dibuat dengan nilai 1:

val number: Int = 1

Tentukan class SmartDevice:

  1. Di Kotlin Playground, ganti konten dengan fungsi main() kosong:
fun main() {
}
  1. Pada baris sebelum fungsi main(), tentukan class SmartDevice dengan isi yang menyertakan komentar // empty body:
class SmartDevice {
    // empty body
}

fun main() {
}

3. Membuat instance class

Seperti yang telah Anda pelajari, class adalah cetak biru untuk objek. Runtime Kotlin menggunakan class, atau cetak biru, untuk membuat objek dari jenis tertentu. Dengan class SmartDevice, Anda memiliki cetak biru tentang pengertian perangkat smart. Untuk memiliki perangkat smart sebenarnya dalam program, Anda harus membuat instance objek SmartDevice. Sintaksis pembuatan instance dimulai dengan nama class, diikuti sepasang tanda kurung seperti yang dapat Anda lihat dalam diagram ini:

1d25bc4f71c31fc9.png

Untuk menggunakan objek, Anda membuat objek dan menetapkannya ke variabel, mirip dengan cara Anda menentukan variabel. Anda menggunakan kata kunci val untuk membuat variabel yang tidak dapat diubah dan kata kunci var untuk variabel yang dapat diubah. Kata kunci val atau var diikuti dengan nama variabel, lalu operator penetapan =, kemudian pembuatan instance objek class. Anda dapat melihat sintaksisnya dalam diagram ini:

f58430542f2081a9.png

Buat instance class SmartDevice sebagai objek:

  • Dalam fungsi main(), gunakan kata kunci val untuk membuat variabel bernama smartTvDevice dan melakukan inisialisasi sebagai instance dari class SmartDevice:
fun main() {
    val smartTvDevice = SmartDevice()
}

4. Menentukan metode class

Di Unit 1, Anda telah mempelajari bahwa:

  • Definisi fungsi menggunakan kata kunci fun, diikuti sepasang tanda kurung dan sepasang tanda kurung kurawal. Kurung kurawal berisi kode yang merupakan petunjuk yang diperlukan untuk mengeksekusi tugas.
  • Pemanggilan fungsi menyebabkan kode yang dimuat dalam fungsi tersebut dieksekusi.

Tindakan yang dapat dilakukan class ditentukan sebagai fungsi di class tersebut. Misalnya, bayangkan Anda memiliki perangkat smart, smart TV, atau lampu smart, yang dapat diaktifkan dan dinonaktifkan dengan ponsel. Perangkat smart diterjemahkan ke class SmartDevice dalam pemrograman, dan tindakan untuk mengaktifkan dan menonaktifkannya direpresentasikan oleh fungsi turnOn() dan turnOff() yang mengaktifkan perilaku aktif dan nonaktif.

Sintaksis untuk menentukan fungsi di class sama dengan yang telah Anda pelajari sebelumnya. Satu-satunya perbedaan adalah fungsi tersebut ditempatkan dalam isi class. Saat Anda menentukan fungsi dalam isi class, fungsi itu disebut sebagai fungsi anggota atau metode, dan merepresentasikan perilaku class. Untuk codelab ini, fungsi disebut sebagai metode setiap kali fungsi tersebut muncul dalam isi class.

Tentukan metode turnOn() dan turnOff() di class SmartDevice:

  1. Dalam isi class SmartDevice, tentukan metode turnOn() dengan isi kosong:
class SmartDevice {
    fun turnOn() {

    }
}
  1. Di bagian isi metode turnOn(), tambahkan pernyataan println(), lalu teruskan string "Smart device is turned on.":
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }
}
  1. Setelah metode turnOn(), tambahkan metode turnOff() yang mencetak string "Smart device is turned off.":
class SmartDevice {
    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

Memanggil metode pada objek

Sejauh ini, Anda sudah menentukan class yang berfungsi sebagai cetak biru untuk perangkat smart, membuat instance class, dan menetapkan instance ke variabel. Sekarang Anda menggunakan metode class SmartDevice untuk mengaktifkan dan menonaktifkan perangkat.

Panggilan ke metode di class serupa dengan cara Anda memanggil fungsi lain dari fungsi main() dalam codelab sebelumnya. Misalnya, jika Anda perlu memanggil metode turnOff() dari metode turnOn(), Anda dapat menulis sesuatu yang mirip dengan cuplikan kode ini:

class SmartDevice {
    fun turnOn() {
        // A valid use case to call the turnOff() method could be to turn off the TV when available power doesn't meet the requirement.
        turnOff()
        ...
    }

    ...
}

Untuk memanggil metode class di luar class, mulai dengan objek class, diikuti dengan operator ., nama fungsi, dan sepasang tanda kurung. Jika diperlukan, tanda kurung dapat berisi argumen yang dibutuhkan oleh metode. Anda dapat melihat sintaksisnya dalam diagram ini:

fc609c15952551ce.png

Panggil metode turnOn() dan turnOff() pada objek:

  1. Pada fungsi main() di baris setelah variabel smartTvDevice, panggil metode turnOn():
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
}
  1. Pada baris setelah metode turnOn(), panggil metode turnOff():
fun main() {
    val smartTvDevice = SmartDevice()
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. Jalankan kode.

Outputnya adalah sebagai berikut:

Smart device is turned on.
Smart device is turned off.

5. Menentukan properti class

Di Unit 1, Anda telah mempelajari variabel, yang merupakan container untuk data-data tunggal. Anda telah mempelajari cara membuat variabel baca-saja dengan kata kunci val dan variabel yang dapat diubah dengan kata kunci var.

Meskipun metode menentukan tindakan yang dapat dilakukan class, properti menentukan atribut data atau karakteristik class. Misalnya, perangkat smart memiliki properti berikut:

  • Nama. Nama perangkat.
  • Kategori. Jenis perangkat smart, seperti hiburan, aplikasi utilitas, atau memasak.
  • Status perangkat. Apakah perangkat aktif, nonaktif, online, atau offline. Perangkat dianggap online jika terhubung ke internet. Jika tidak, perangkat akan dianggap offline.

Pada dasarnya, properti adalah variabel yang ditentukan dalam isi class, bukan isi fungsi. Ini berarti sintaksis untuk menentukan properti dan variabel identik. Anda menentukan properti yang tidak dapat diubah dengan kata kunci val dan properti yang dapat diubah dengan kata kunci var.

Terapkan karakteristik yang disebutkan di atas sebagai properti class SmartDevice:

  1. Pada baris sebelum metode turnOn(), tentukan properti name dan tetapkan properti tersebut ke string "Android TV":
class SmartDevice {

    val name = "Android TV"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. Pada baris setelah properti name, tentukan properti category dan tetapkan ke string "Entertainment", lalu tentukan properti deviceStatus dan tetapkan ke string "online":
class SmartDevice {

    val name = "Android TV"
    val category = "Entertainment"
    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}
  1. Pada baris setelah variabel smartTvDevice, panggil fungsi println(), lalu teruskan string "Device name is: ${smartTvDevice.name}":
fun main() {
    val smartTvDevice = SmartDevice()
    println("Device name is: ${smartTvDevice.name}")
    smartTvDevice.turnOn()
    smartTvDevice.turnOff()
}
  1. Jalankan kode.

Outputnya adalah sebagai berikut:

Device name is: Android TV
Smart device is turned on.
Smart device is turned off.

Fungsi pengambil dan penyetel dalam properti

Properti dapat melakukan lebih dari yang dilakukan variabel. Misalnya, bayangkan Anda membuat struktur class untuk merepresentasikan smart TV. Salah satu tindakan umum yang Anda lakukan adalah meningkatkan dan menurunkan volume. Untuk merepresentasikan tindakan ini dalam pemrograman, Anda dapat membuat properti bernama speakerVolume yang menyimpan tingkat volume saat ini yang disetel di speaker TV, tetapi ada rentang tempat nilai volume disimpan. Volume minimum yang dapat disetel adalah 0, sedangkan volume maksimum adalah 100. Untuk memastikan properti speakerVolume tidak pernah melampaui 100 atau turun di bawah 0, Anda dapat menulis fungsi penyetel. Saat mengupdate nilai properti, Anda harus memeriksa apakah nilai berada dalam rentang 0 hingga 100. Contoh lainnya, bayangkan ada persyaratan untuk memastikan bahwa nama selalu dalam huruf kapital. Anda dapat menerapkan fungsi pengambil untuk mengonversi properti name menjadi huruf kapital.

Sebelum mempelajari lebih lanjut cara menerapkan properti ini, Anda perlu memahami sintaksis lengkap untuk mendeklarasikannya. Sintaksis lengkap untuk menentukan properti yang dapat diubah dimulai dengan definisi variabel, diikuti dengan fungsi get() dan set() opsional. Anda dapat melihat sintaksisnya dalam diagram ini:

f2cf50a63485599f.png

Jika Anda tidak menentukan fungsi pengambil dan penyetel untuk properti, compiler Kotlin akan membuat fungsi secara internal. Misalnya, jika Anda menggunakan kata kunci var untuk menentukan properti speakerVolume dan memberinya nilai 2, compiler akan otomatis membuat fungsi pengambil dan penyetel seperti yang dapat Anda lihat di cuplikan kode ini:

var speakerVolume = 2
    get() = field  
    set(value) {
        field = value    
    }

Anda tidak akan melihat baris ini dalam kode Anda karena baris tersebut ditambahkan oleh compiler di latar belakang.

Sintaksis lengkap untuk properti yang tidak dapat diubah memiliki dua perbedaan:

  • Dimulai dengan kata kunci val.
  • Variabel jenis val adalah variabel baca-saja sehingga tidak memiliki fungsi set().

Properti Kotlin menggunakan kolom pendukung untuk menyimpan nilai di memori. Kolom pendukung pada dasarnya adalah variabel class yang ditentukan secara internal di properti. Kolom pendukung dibatasi untuk properti, yang berarti Anda hanya dapat mengaksesnya melalui fungsi properti get() atau set().

Untuk membaca nilai properti dalam fungsi get() atau mengupdate nilai di fungsi set(), Anda harus menggunakan kolom pendukung properti. Kolom pendukung ini otomatis dibuat oleh compiler Kotlin dan direferensikan dengan ID field.

Misalnya, saat Anda ingin mengupdate nilai properti dalam fungsi set(), gunakan parameter fungsi set() yang disebut sebagai parameter value, dan tetapkan ke variabel field seperti yang dapat Anda lihat di cuplikan kode ini:

var speakerVolume = 2
    set(value) {
        field = value    
    }

Misalnya, untuk memastikan nilai yang ditetapkan ke properti speakerVolume berada dalam rentang 0 hingga 100, Anda dapat menerapkan fungsi penyetel seperti yang dapat Anda lihat di cuplikan kode ini:

var speakerVolume = 2
    set(value) {
        if (value in 0..100) {
            field = value
        }
    }

Fungsi set() memeriksa apakah nilai Int berada dalam rentang 0 hingga 100 dengan menggunakan kata kunci in, diikuti dengan rentang nilai. Jika nilai berada dalam rentang yang diharapkan, nilai field akan diupdate. Jika tidak, nilai properti tidak berubah.

Anda akan menyertakan properti ini di class di bagian Mengimplementasikan hubungan antar-class dalam codelab ini, sehingga Anda tidak perlu menambahkan fungsi penyetel ke kode sekarang.

6. Menentukan konstruktor

Tujuan utama konstruktor adalah menentukan cara pembuatan objek class. Dengan kata lain, konstruktor melakukan inisialisasi objek dan menyiapkan objek tersebut untuk digunakan. Anda melakukannya saat membuat instance objek. Kode di dalam konstruktor dieksekusi ketika objek class dibuat instance-nya. Anda dapat menentukan konstruktor dengan atau tanpa parameter.

Konstruktor default

Konstruktor default adalah konstruktor tanpa parameter. Anda dapat menentukan konstruktor default seperti yang ditunjukkan dalam cuplikan kode ini:

class SmartDevice constructor() {
    ...
}

Kotlin bertujuan agar ringkas sehingga Anda dapat menghapus kata kunci constructor jika tidak ada anotasi atau pengubah visibilitas, yang akan segera Anda pelajari di konstruktor. Anda juga dapat menghapus tanda kurung jika konstruktor tidak memiliki parameter seperti yang ditunjukkan dalam cuplikan kode ini:

class SmartDevice {
    ...
}

Compiler Kotlin secara otomatis membuat konstruktor default. Anda tidak akan melihat konstruktor default yang dibuat secara otomatis dalam kode Anda karena ditambahkan oleh compiler di latar belakang.

Menentukan konstruktor berparameter

Di class SmartDevice, properti name dan category tidak dapat diubah. Anda harus memastikan semua instance class SmartDevice melakukan inisialisasi pada properti name dan category. Dengan implementasi saat ini, nilai untuk properti name dan category di-hardcode. Ini berarti bahwa semua perangkat smart diberi nama dengan string "Android TV" dan dikategorikan dengan string "Entertainment".

Untuk mempertahankan sifatnya yang tidak dapat diubah tetapi menghindari nilai hardcode, gunakan konstruktor berparameter untuk melakukan inisialisasi terhadapnya:

  • Di class SmartDevice, pindahkan properti name dan category ke konstruktor tanpa menetapkan nilai default:
class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    fun turnOn() {
        println("Smart device is turned on.")
    }

    fun turnOff() {
        println("Smart device is turned off.")
    }
}

Konstruktor sekarang menerima parameter untuk menyiapkan propertinya, sehingga cara membuat instance objek untuk class tersebut juga berubah. Anda dapat melihat sintaksis lengkap untuk membuat instance objek dalam diagram ini:

bbe674861ec370b6.png

Berikut ini representasi kode:

SmartDevice("Android TV", "Entertainment")

Kedua argumen untuk konstruktor adalah string. Kurang jelas parameter mana yang nilainya harus ditetapkan. Untuk mengatasinya, serupa dengan cara meneruskan argumen fungsi, Anda dapat membuat konstruktor dengan argumen bernama seperti yang ditunjukkan dalam cuplikan kode ini:

SmartDevice(name = "Android TV", category = "Entertainment")

Ada dua jenis utama konstruktor di Kotlin:

  • Konstruktor utama. Class hanya dapat memiliki satu konstruktor utama, yang didefinisikan sebagai bagian dari header class. Konstruktor utama dapat berupa konstruktor default atau berparameter. Konstruktor utama tidak memiliki isi. Artinya, konstruktor tersebut tidak boleh berisi kode apa pun.
  • Konstruktor sekunder. Class dapat memiliki beberapa konstruktor sekunder. Anda dapat menentukan konstruktor sekunder dengan atau tanpa parameter. Konstruktor sekunder dapat melakukan inisialisasi pada class dan memiliki isi yang dapat berisi logika inisialisasi. Jika class memiliki konstruktor utama, setiap konstruktor sekunder perlu melakukan inisialisasi pada konstruktor utama.

Anda dapat menggunakan konstruktor utama untuk melakukan inisialisasi pada properti di header class. Argumen yang diteruskan ke konstruktor ditetapkan ke properti. Sintaksis untuk menentukan konstruktor utama dimulai dengan nama class, diikuti dengan kata kunci constructor dan sepasang tanda kurung. Tanda kurung berisi parameter untuk konstruktor utama. Jika ada lebih dari satu parameter, pisahkan definisi parameter dengan koma. Anda dapat melihat sintaksis lengkap untuk menentukan konstruktor utama dalam diagram ini:

aa05214860533041.png

Konstruktor sekunder dimasukkan dalam isi class, dan sintaksisnya mencakup tiga bagian:

  • Deklarasi konstruktor sekunder. Definisi konstruktor sekunder dimulai dengan kata kunci constructor, diikuti dengan tanda kurung. Jika diperlukan, tanda kurung dapat berisi parameter yang dibutuhkan oleh konstruktor sekunder.
  • Inisialisasi konstruktor utama. Inisialisasi dimulai dengan titik dua, diikuti kata kunci this dan sepasang tanda kurung. Jika diperlukan, tanda kurung dapat berisi parameter yang dibutuhkan oleh konstruktor utama.
  • Isi konstruktor sekunder. Inisialisasi konstruktor utama diikuti oleh sepasang tanda kurung kurawal yang memuat isi konstruktor sekunder.

Anda dapat melihat sintaksisnya dalam diagram ini:

2dc13ef136009e98.png

Misalnya, Anda ingin mengintegrasikan API yang dikembangkan oleh penyedia perangkat smart. Namun, API tersebut menampilkan kode status jenis Int untuk menunjukkan status perangkat awal. API menampilkan nilai 0 jika perangkat offline dan nilai 1 jika perangkat online. Untuk nilai integer lainnya, status dianggap tidak diketahui. Anda dapat membuat konstruktor sekunder di class SmartDevice untuk mengonversi parameter statusCode ini menjadi representasi string seperti yang dapat Anda lihat di cuplikan kode ini:

class SmartDevice(val name: String, val category: String) {
    var deviceStatus = "online"

    constructor(name: String, category: String, statusCode: Int) : this(name, category) {
        deviceStatus = when (statusCode) {
            0 -> "offline"
            1 -> "online"
            else -> "unknown"
        }
    }
    ...
}

7. Mengimplementasikan hubungan antar-class

Pewarisan memungkinkan Anda membuat class berdasarkan karakteristik dan perilaku class lain. Ini adalah mekanisme canggih yang membantu Anda menulis kode yang dapat digunakan kembali dan membangun hubungan antar-class.

Misalnya, ada banyak perangkat smart di pasar, seperti smart TV, lampu smart, dan tombol smart. Saat Anda merepresentasikan perangkat smart dalam pemrograman, perangkat tersebut memiliki beberapa properti yang sama, seperti nama, kategori, dan status. Perangkat tersebut juga memiliki perilaku yang sama, seperti kemampuan untuk mengaktifkan dan menonaktifkannya.

Namun, cara untuk mengaktifkan atau menonaktifkan setiap perangkat smart berbeda. Misalnya, untuk mengaktifkan TV, Anda mungkin perlu mengaktifkan layar, lalu menyiapkan tingkat volume dan saluran terakhir yang diketahui. Di sisi lain, untuk menyalakan lampu, Anda mungkin hanya perlu meningkatkan atau menurunkan kecerahan.

Selain itu, setiap perangkat smart memiliki lebih banyak fungsi dan tindakan yang dapat dilakukan. Misalnya, dengan TV, Anda dapat menyesuaikan volume dan mengubah saluran. Dengan lampu, Anda dapat menyesuaikan kecerahan atau warna.

Singkatnya, semua perangkat smart memiliki fitur yang berbeda-beda, tetapi memiliki beberapa karakteristik yang sama. Anda dapat menduplikasi karakteristik yang sama ini ke setiap class perangkat smart atau membuat kode dapat digunakan kembali dengan pewarisan.

Untuk melakukannya, Anda harus membuat class induk SmartDevice, serta menentukan properti dan perilaku yang sama. Kemudian, Anda dapat membuat class turunan, seperti class SmartTvDevice dan SmartLightDevice, yang mewarisi properti class induk.

Dalam istilah pemrograman, kita mengatakan bahwa class SmartTvDevice dan SmartLightDevice memperluas class induk SmartDevice. Class induk juga disebut sebagai superclass dan class turunan sebagai subclass. Anda dapat melihat hubungan antara keduanya dalam diagram ini:

Diagram yang merepresentasikan hubungan pewarisan antar-class.

Namun, dalam Kotlin, semua class bersifat final secara default, yang berarti Anda tidak dapat memperluasnya, jadi Anda harus menentukan hubungan antar-class.

Tentukan hubungan antara superclass SmartDevice dan subclass-nya:

  1. Di superclass SmartDevice, tambahkan kata kunci open sebelum kata kunci class agar dapat diperluas:
open class SmartDevice(val name: String, val category: String) {
    ...
}

Kata kunci open memberi tahu compiler bahwa class ini dapat diperluas, sehingga sekarang class lain dapat memperluasnya.

Sintaksis untuk membuat subclass dimulai dengan pembuatan header class seperti yang telah Anda lakukan sejauh ini. Kurung tutup konstruktor diikuti dengan spasi, titik dua, spasi lain, nama superclass, dan sepasang tanda kurung. Jika diperlukan, tanda kurung dapat menyertakan parameter yang dibutuhkan oleh konstruktor superclass. Anda dapat melihat sintaksisnya dalam diagram ini:

1ac63b66e6b5c224.png

  1. Buat subclass SmartTvDevice yang memperluas superclass SmartDevice:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}

Definisi constructor untuk SmartTvDevice tidak menentukan apakah properti dapat diubah atau tidak dapat diubah. Ini berarti parameter deviceName dan deviceCategory hanyalah parameter constructor, bukan properti class. Anda tidak akan dapat menggunakannya di class, tetapi cukup teruskan ke konstruktor superclass.

  1. Di bagian isi subclass SmartTvDevice, tambahkan properti speakerVolume yang Anda buat saat mempelajari fungsi pengambil dan penyetel:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. Tentukan properti channelNumber yang ditetapkan ke nilai 1 dengan fungsi penyetel yang menentukan rentang 0..200:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
}
  1. Tentukan metode increaseSpeakerVolume() yang meningkatkan volume dan mencetak string "Speaker volume increased to $speakerVolume.":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    } 
}
  1. Tambahkan metode nextChannel() yang meningkatkan jumlah saluran dan mencetak string "Channel number increased to $channelNumber.":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
    
    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }
}
  1. Pada baris setelah subclass SmartTvDevice, tentukan subclass SmartLightDevice yang memperluas superclass SmartDevice:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {
}
  1. Dalam isi subclass SmartLightDevice, tentukan properti brightnessLevel yang ditetapkan ke nilai 0 dengan fungsi penyetel yang menentukan rentang 0..100:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
}
  1. Tentukan metode increaseBrightness() yang meningkatkan kecerahan lampu dan mencetak string "Brightness increased to $brightnessLevel.":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }
}

Hubungan antar-class

Jika menggunakan pewarisan, Anda akan menetapkan hubungan antara dua class dalam sesuatu yang disebut hubungan IS-A. Objek juga merupakan instance class yang memberikan warisan. Dalam hubungan HAS-A, objek dapat memiliki instance class lain tanpa benar-benar menjadi instance class itu sendiri. Anda dapat melihat representasi tingkat tinggi dari hubungan ini dalam diagram ini:

Representasi tingkat tinggi dari hubungan HAS-A dan IS-A.

Hubungan IS-A

Saat Anda menentukan hubungan IS-A antara superclass SmartDevice dan subclass SmartTvDevice, artinya, apa pun yang dapat dilakukan oleh superclass SmartDevice dapat juga dilakukan oleh subclass SmartTvDevice. Hubungannya bersifat satu arah, sehingga Anda dapat mengatakan bahwa setiap smart TV adalah perangkat smart, tetapi Anda tidak dapat mengatakan bahwa setiap perangkat smart adalah smart TV. Representasi kode untuk hubungan IS-A ditunjukkan dalam cuplikan kode ini:

// Smart TV IS-A smart device.
class SmartTvDevice : SmartDevice() {
}

Jangan gunakan pewarisan hanya untuk mencapai penggunaan kembali kode. Sebelum memutuskan, periksa apakah kedua class tersebut saling berhubungan. Jika keduanya menunjukkan adanya hubungan, periksa apakah keduanya benar-benar memenuhi syarat untuk hubungan IS-A. Tanyakan pada diri sendiri, "Bisakah saya katakan bahwa subclass adalah superclass?". Misalnya, Android adalah sistem operasi.

Hubungan HAS-A

Hubungan HAS-A adalah cara lain untuk menentukan hubungan antara dua kelas. Misalnya, Anda mungkin akan menggunakan smart TV di rumah Anda. Dalam hal ini, ada hubungan antara smart TV dan rumah. Rumah berisi perangkat smart atau, dengan kata lain, rumah memiliki perangkat smart. Hubungan HAS-A antara dua class juga disebut sebagai komposisi.

Sejauh ini, Anda telah membuat beberapa perangkat smart. Sekarang, Anda akan membuat class SmartHome yang berisi perangkat smart. Class SmartHome memungkinkan Anda berinteraksi dengan perangkat smart.

Gunakan hubungan HAS-A untuk menentukan class SmartHome:

  1. Di antara class SmartLightDevice dan fungsi main(), tentukan class SmartHome:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

}

class SmartHome {
}

fun main() { 
    ...
}
  1. Pada konstruktor class SmartHome, gunakan kata kunci val untuk membuat properti smartTvDevice dari jenis SmartTvDevice:
// The SmartHome class HAS-A smart TV device.
class SmartHome(val smartTvDevice: SmartTvDevice) {

}
  1. Dalam isi class SmartHome, tentukan metode turnOnTv() yang memanggil metode turnOn() di properti smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }
}
  1. Pada baris setelah metode turnOnTv(), tentukan metode turnOffTv() yang memanggil metode turnOff() di properti smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

}
  1. Pada baris setelah metode turnOffTv(), tentukan metode increaseTvVolume() yang memanggil metode increaseSpeakerVolume() di properti smartTvDevice, lalu tentukan metode changeTvChannelToNext() yang memanggil metode nextChannel() di properti smartTvDevice:
class SmartHome(val smartTvDevice: SmartTvDevice) {

    fun turnOnTv() {
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }
}
  1. Pada konstruktor class SmartHome, pindahkan parameter properti smartTvDevice ke barisnya sendiri, diikuti dengan koma:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
) {

    ...

}
  1. Pada baris setelah properti smartTvDevice, gunakan kata kunci val untuk menentukan properti smartLightDevice dari jenis SmartLightDevice:
// The SmartHome class HAS-A smart TV device and smart light.
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

}
  1. Di bagian isi SmartHome, tentukan metode turnOnLight() yang memanggil metode turnOn() pada objek smartLightDevice dan metode turnOffLight() yang memanggil metode turnOff() pada objek smartLightDevice:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }
}
  1. Pada baris setelah metode turnOffLight(), tentukan metode increaseLightBrightness() yang memanggil metode increaseBrightness() di properti smartLightDevice:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }
}
  1. Pada baris setelah metode increaseLightBrightness(), tentukan metode turnOffAllDevices() yang memanggil metode turnOffTv() dan turnOffLight():
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    ...

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

Mengganti metode superclass dari subclass

Seperti yang telah dibahas sebelumnya, meskipun fungsi pengaktifan dan penonaktifan didukung oleh semua perangkat smart, cara kerja fungsi tersebut berbeda. Untuk menyediakan perilaku khusus perangkat ini, Anda perlu mengganti metode turnOn() dan turnOff() yang ditentukan di superclass. Mengganti berarti mencegat tindakan, biasanya untuk mengambil kontrol manual. Saat Anda mengganti metode, metode di subclass akan mengganggu eksekusi metode yang ditentukan di superclass dan menyediakan eksekusinya sendiri.

Ganti metode turnOn() dan turnOff() class SmartDevice:

  1. Di bagian isi superclass SmartDevice, sebelum kata kunci fun setiap metode, tambahkan kata kunci open:
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        // function body
    }

    open fun turnOff() {
        // function body
    }
}
  1. Dalam isi class SmartLightDevice, tentukan metode turnOn() dengan isi kosong:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
    }
}
  1. Di bagian isi metode turnOn(), atur properti deviceStatus ke string "on", setel properti brightnessLevel menjadi nilai 2, lalu tambahkan pernyataan println() dan teruskan string "$name turned on. The brightness level is $brightnessLevel.":
    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }
  1. Dalam isi class SmartLightDevice, tentukan metode turnOff() dengan isi kosong:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
    }
}
  1. Di bagian isi metode turnOff(), atur properti deviceStatus ke string "off", setel properti brightnessLevel menjadi nilai 0, lalu tambahkan pernyataan println() dan teruskan string "Smart Light turned off":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}
  1. Di subclass SmartLightDevice, sebelum kata kunci fun dari metode turnOn() dan turnOff(), tambahkan kata kunci override:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        deviceStatus = "off"
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

Kata kunci override memberi tahu runtime Kotlin untuk mengeksekusi kode yang disertakan dalam metode yang ditentukan di subclass.

  1. Dalam isi class SmartTvDevice, tentukan metode turnOn() dengan isi kosong:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }
        
    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }
        
    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }
    
    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    fun turnOn() {
    }
}
  1. Di bagian isi metode turnOn(), setel properti deviceStatus ke string "on" dan tambahkan pernyataan println(), lalu teruskan string "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber.":
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }
}
  1. Di bagian isi class SmartTvDevice setelah metode turnOn(), tentukan metode turnOff() dengan isi kosong:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
    }
}
  1. Di bagian isi metode turnOff(), setel properti deviceStatus ke string "off" dan tambahkan pernyataan println(), lalu teruskan string "$name turned off":
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    fun turnOn() {
        ...
    }

    fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. Di class SmartTvDevice sebelum kata kunci fun dari metode turnOn() dan turnOff(), tambahkan kata kunci override:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        deviceStatus = "on"
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        deviceStatus = "off"
        println("$name turned off")
    }
}
  1. Dalam fungsi main(), gunakan kata kunci var untuk menentukan variabel smartDevice dari jenis SmartDevice yang membuat instance objek SmartTvDevice yang menggunakan argumen "Android TV" dan argumen "Entertainment":
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
}
  1. Pada baris setelah variabel smartDevice, panggil metode turnOn() pada objek smartDevice:
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
}
  1. Jalankan kode.

Outputnya adalah sebagai berikut:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
  1. Pada baris setelah panggilan ke metode turnOn(), tetapkan ulang variabel smartDevice untuk membuat instance class SmartLightDevice yang menggunakan argumen "Google Light" dan argumen "Utility", lalu panggil metode turnOn() pada referensi objek smartDevice:
fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()
    
    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}
  1. Jalankan kode.

Outputnya adalah sebagai berikut:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

Ini adalah contoh polimorfisme. Kode memanggil metode turnOn() pada variabel jenis SmartDevice dan, bergantung pada nilai sebenarnya dari variabel itu, implementasi metode turnOn() yang berbeda dapat dieksekusi.

Menggunakan kembali kode superclass di subclass dengan kata kunci super

Jika metode turnOn() dan turnOff() dilihat lebih teliti, Anda akan melihat adanya kesamaan dalam cara mengupdate variabel deviceStatus setiap kali metode tersebut dipanggil di subclass SmartTvDevice dan SmartLightDevice: kode akan diduplikasi. Anda dapat menggunakan kembali kode tersebut saat memperbarui status di class SmartDevice.

Untuk memanggil metode yang diganti di superclass dari subclass, Anda harus menggunakan kata kunci super. Memanggil metode dari superclass sama halnya dengan memanggil metode dari luar class. Daripada menggunakan operator . antara objek dan metode, Anda harus menggunakan kata kunci super yang memberi tahu compiler Kotlin untuk memanggil metode di superclass, bukan subclass.

Sintaksis untuk memanggil metode dari superclass dimulai dengan kata kunci super, diikuti dengan operator ., nama fungsi, dan sepasang tanda kurung. Jika diperlukan, tanda kurung dapat menyertakan argumen. Anda dapat melihat sintaksisnya dalam diagram ini:

18cc94fefe9851e0.png

Gunakan kembali kode dari superclass SmartDevice:

  1. Hapus pernyataan println() dari metode turnOn() dan turnOff(), lalu pindahkan kode duplikat dari subclass SmartTvDevice dan SmartLightDevice ke superclass SmartDevice:
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}
  1. Gunakan kata kunci super untuk memanggil metode dari class SmartDevice di subclass SmartTvDevice dan SmartLightDevice:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

     var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

Mengganti properti superclass dari subclass

Serupa dengan metode, Anda juga dapat mengganti properti dengan langkah yang sama.

Ganti properti deviceType:

  1. Di superclass SmartDevice pada baris setelah properti deviceStatus, gunakan kata kunci open dan val untuk menentukan properti deviceType yang disetel ke string "unknown":
open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"

    open val deviceType = "unknown"
    ...
}
  1. Di class SmartTvDevice, gunakan kata kunci override dan val untuk menentukan properti deviceType yang disetel ke string "Smart TV":
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    ...
}
  1. Di class SmartLightDevice, gunakan kata kunci override dan val untuk menentukan properti deviceType yang disetel ke string "Smart Light":
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    ...

}

8. Pengubah visibilitas

Pengubah visibilitas memainkan peran penting untuk mencapai enkapsulasi:

  • Di class, fungsi ini memungkinkan Anda menyembunyikan properti dan metode dari akses tidak sah di luar class.
  • Dalam paket, fungsi ini memungkinkan Anda menyembunyikan class dan antarmuka dari akses tidak sah di luar paket.

Kotlin menyediakan empat pengubah visibilitas:

  • public. Pengubah visibilitas default. Menjadikan deklarasi dapat diakses di mana saja. Properti dan metode yang ingin Anda gunakan di luar class ditandai sebagai publik.
  • private. Membuat deklarasi dapat diakses di class atau file sumber yang sama.

Mungkin ada beberapa properti dan metode yang hanya digunakan di dalam class, dan Anda tidak ingin class lain menggunakannya. Properti dan metode ini dapat ditandai dengan pengubah visibilitas private untuk memastikan class lain tidak dapat mengaksesnya secara tidak sengaja.

  • protected. Membuat deklarasi dapat diakses di subclass. Properti dan metode yang ingin Anda gunakan di class yang menentukannya dan subclass ditandai dengan pengubah visibilitas protected.
  • internal. Membuat deklarasi dapat diakses di modul yang sama. Pengubah internal serupa dengan pribadi, tetapi Anda dapat mengakses properti dan metode internal dari luar class selama dapat diakses dalam modul yang sama.

Jika Anda menentukan class, class tersebut akan terlihat secara publik dan dapat diakses oleh paket apa pun yang mengimpornya, yang berarti class tersebut bersifat publik secara default, kecuali Anda menetapkan pengubah visibilitas. Demikian pula, saat Anda mendefinisikan atau mendeklarasikan properti dan metode di class, secara default keduanya dapat diakses di luar class melalui objek class. Anda harus menentukan visibilitas kode yang tepat, terutama untuk menyembunyikan properti dan metode yang tidak perlu diakses oleh class lain.

Misalnya, pertimbangkan bagaimana mobil dapat diakses oleh pengemudi. Detail tentang bagian-bagian mobil dan cara kerja mobil secara internal disembunyikan secara default. Mobil itu harus sebisa mungkin berfungsi secara intuitif. Anda tentu tidak ingin mobil itu berfungsi sekompleks pesawat komersial, sama halnya dengan Anda yang tidak ingin developer lain atau diri Anda di masa depan bingung mengenai properti dan metode class yang harus digunakan.

Pengubah visibilitas membantu Anda menampilkan bagian kode yang relevan ke class lain dalam project Anda dan memastikan implementasi tersebut tidak dapat digunakan secara tidak sengaja, sehingga membuat kode mudah dipahami dan tidak terlalu rentan terhadap bug.

Saat mendeklarasikan class, metode, atau properti, pengubah visibilitas harus ditempatkan sebelum sintaksis deklarasi, seperti yang dapat Anda lihat dalam diagram ini:

dcc4f6693bf719a9.png

Menentukan pengubah visibilitas untuk properti

Sintaksis untuk menentukan pengubah visibilitas untuk properti dimulai dengan pengubah private, protected, atau internal, diikuti dengan sintaksis yang menentukan properti. Anda dapat melihat sintaksisnya dalam diagram ini:

47807a890d237744.png

Misalnya, Anda dapat melihat cara menjadikan properti deviceStatus bersifat pribadi dalam cuplikan kode ini:

open class SmartDevice(val name: String, val category: String) {

    ...

    private var deviceStatus = "online"

    ...
}

Anda juga dapat menetapkan pengubah visibilitas ke fungsi penyetel. Pengubah ditempatkan sebelum kata kunci set. Anda dapat melihat sintaksisnya dalam diagram ini:

cea29a49b7b26786.png

Untuk class SmartDevice, nilai properti deviceStatus harus dapat dibaca di luar class melalui objek class. Namun, hanya class dan turunannya yang dapat mengupdate atau menulis nilai tersebut. Untuk menerapkan persyaratan ini, Anda harus menggunakan pengubah protected pada fungsi set() properti deviceStatus.

Gunakan pengubah protected pada fungsi set() properti deviceStatus:

  1. Di properti deviceStatus superclass SmartDevice, tambahkan pengubah protected ke fungsi set():
open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set(value) {
           field = value
       }

    ...
}

Anda tidak melakukan tindakan atau pemeriksaan apa pun dalam fungsi set(). Anda cukup menetapkan parameter value ke variabel field. Seperti yang telah Anda pelajari sebelumnya, ini serupa dengan implementasi default untuk penyetel properti. Dalam hal ini, Anda dapat menghilangkan tanda kurung dan isi dari fungsi set():

open class SmartDevice(val name: String, val category: String) {

    ...

    var deviceStatus = "online"
        protected set

    ...
}
  1. Di class SmartHome, tentukan properti deviceTurnOnCount yang disetel ke nilai 0 dengan fungsi penyetel pribadi:
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    ...
}
  1. Tambahkan properti deviceTurnOnCount, diikuti dengan operator aritmetika ++ ke metode turnOnTv() dan turnOnLight(), lalu tambahkan properti deviceTurnOnCount, diikuti dengan operator aritmetika -- ke metode turnOffTv() dan turnOffLight():
class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }
    
    ...

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    ...

}

Pengubah visibilitas untuk metode

Sintaksis untuk menentukan pengubah visibilitas untuk metode dimulai dengan pengubah private, protected, atau internal, diikuti dengan sintaksis yang menentukan metode. Anda dapat melihat sintaksisnya dalam diagram ini:

e0a60ddc26b841de.png

Misalnya, Anda dapat melihat cara menentukan pengubah protected untuk metode nextChannel() di class SmartTvDevice dalam cuplikan kode ini:

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    protected fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }      

    ...
}

Pengubah visibilitas untuk konstruktor

Sintaksis untuk menentukan pengubah visibilitas bagi konstruktor serupa dengan menentukan konstruktor utama dengan beberapa perbedaan:

  • Pengubah ditentukan setelah nama class, tetapi sebelum kata kunci constructor.
  • Jika Anda perlu menentukan pengubah untuk konstruktor utama, tanda kurung dan kata kunci constructor harus dipertahankan meskipun tidak ada parameter.

Anda dapat melihat sintaksisnya dalam diagram ini:

6832575eba67f059.png

Misalnya, Anda dapat melihat cara menambahkan pengubah protected ke konstruktor SmartDevice dalam cuplikan kode ini:

open class SmartDevice protected constructor (val name: String, val category: String) {

    ...

}

Pengubah visibilitas untuk class

Sintaksis untuk menentukan pengubah visibilitas bagi class dimulai dengan pengubah private, protected, atau internal, diikuti dengan sintaksis yang menentukan class. Anda dapat melihat sintaksisnya dalam diagram ini:

3ab4aa1c94a24a69.png

Misalnya, Anda dapat melihat cara menentukan pengubah internal untuk class SmartDevice dalam cuplikan kode ini:

internal open class SmartDevice(val name: String, val category: String) {

    ...

}

Idealnya, Anda harus berusaha mendapatkan visibilitas properti dan metode yang ketat, jadi deklarasikan dengan pengubah private sesering mungkin. Jika Anda tidak dapat membuatnya tetap pribadi, gunakan pengubah protected. Jika Anda tidak dapat melindunginya, gunakan pengubah internal. Jika Anda tidak dapat menyimpannya secara internal, gunakan pengubah public.

Menentukan pengubah visibilitas yang sesuai

Tabel ini membantu Anda menentukan pengubah visibilitas yang sesuai berdasarkan tempat properti atau metode class atau konstruktor dapat diakses:

Pengubah

Dapat diakses di class yang sama

Dapat diakses di subclass

Dapat diakses dalam modul yang sama

Modul di luar yang dapat diakses

private

𝗫

𝗫

𝗫

protected

𝗫

𝗫

internal

𝗫

public

Di subclass SmartTvDevice, Anda tidak boleh mengizinkan properti speakerVolume dan channelNumber dikontrol dari luar class. Properti ini seharusnya hanya dikontrol melalui metode increaseSpeakerVolume() dan nextChannel().

Demikian pula, dalam subclass SmartLightDevice, properti brightnessLevel hanya boleh dikontrol melalui metode increaseLightBrightness().

Tambahkan pengubah visibilitas yang sesuai ke subclass SmartTvDevice dan SmartLightDevice:

  1. Di class SmartTvDevice, tambahkan pengubah visibilitas private ke properti speakerVolume dan channelNumber:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    private var speakerVolume = 2
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    private var channelNumber = 1
        set(value) {
            if (value in 0..200) {
                field = value
            }
        }

    ...
}
  1. Di class SmartLightDevice, tambahkan pengubah private ke properti brightnessLevel:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    ...

    private var brightnessLevel = 0
        set(value) {
            if (value in 0..100) {
                field = value
            }
        }

    ...
}

9. Menentukan delegasi properti

Anda telah mempelajari di bagian sebelumnya bahwa properti di Kotlin menggunakan kolom pendukung untuk menyimpan nilainya di memori. Anda menggunakan ID field untuk mereferensikannya.

Jika melihat kode sejauh ini, Anda dapat melihat kode duplikat untuk memeriksa apakah nilai berada dalam rentang untuk properti speakerVolume, channelNumber, dan brightnessLevel di class SmartTvDevice dan SmartLightDevice. Anda dapat menggunakan kembali kode pemeriksaan rentang dalam fungsi penyetel dengan delegasi. Sebagai ganti penggunaan kolom, serta fungsi pengambil dan penyetel untuk mengelola nilai, delegasi akan mengelolanya.

Sintaksis untuk membuat delegasi properti dimulai dengan deklarasi variabel, diikuti dengan kata kunci by, dan objek delegasi yang menangani fungsi pengambil dan penyetel untuk properti. Anda dapat melihat sintaksisnya dalam diagram ini:

928547ad52768115.png

Sebelum mengimplementasikan class tempat Anda dapat mendelegasikan implementasi, Anda harus memahami antarmuka. Antarmuka adalah kontrak yang diterapkan oleh class yang mengimplementasikannya. Antarmuka berfokus pada apa yang harus dilakukan, bukan cara melakukan tindakan. Singkatnya, antarmuka membantu Anda mencapai abstraksi.

Misalnya, sebelum membangun rumah, Anda memberi tahu arsitek tentang hal yang Anda inginkan. Anda menginginkan kamar tidur, kamar anak, ruang keluarga, dapur, dan beberapa kamar mandi. Singkatnya, Anda menentukan yang Anda inginkan dan arsitek menentukan cara mencapainya. Anda dapat melihat sintaksis untuk membuat antarmuka dalam diagram ini:

bfe3fd1cd8c45b2a.png

Anda telah mempelajari cara memperluas class dan mengganti fungsinya. Dengan antarmuka, class mengimplementasikan antarmuka. Class ini memberikan detail implementasi untuk metode dan properti yang dideklarasikan di antarmuka. Anda akan melakukan sesuatu yang serupa dengan antarmuka ReadWriteProperty untuk membuat delegasi. Anda akan mempelajari lebih lanjut tentang antarmuka di unit berikutnya.

Untuk membuat class delegasi untuk jenis var, Anda harus mengimplementasikan antarmuka ReadWriteProperty. Demikian pula, Anda harus mengimplementasikan antarmuka ReadOnlyProperty untuk jenis val.

Buat delegasi untuk jenis var:

  1. Sebelum fungsi main(), buat class RangeRegulator yang mengimplementasikan antarmuka ReadWriteProperty<Any?, Int>:
class RangeRegulator() : ReadWriteProperty<Any?, Int> {

}

fun main() {
    ...
}

Jangan khawatir tentang kurung sudut atau konten di dalamnya. Keduanya merepresentasikan jenis generik dan Anda akan mempelajarinya di unit berikutnya.

  1. Di konstruktor utama class RangeRegulator, tambahkan parameter initialValue, properti minValue pribadi, dan properti maxValue pribadi, semuanya menggunakan jenis Int:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

}
  1. Dalam isi class RangeRegulator, ganti metode getValue() dan setValue():
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

Metode ini bertindak sebagai fungsi pengambil dan penyetel properti.

  1. Pada baris sebelum class SmartDevice, impor antarmuka ReadWriteProperty dan KProperty:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {
    ...
}

...

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

...
  1. Di class RangeRegulator, pada baris sebelum metode getValue(), tentukan properti fieldData dan lakukan inisialisasi properti dengan parameter initialValue:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}

Properti ini bertindak sebagai kolom pendukung untuk variabel.

  1. Dalam isi metode getValue(), tampilkan properti fieldData:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    }
}
  1. Dalam isi metode setValue(), periksa apakah parameter value yang ditetapkan berada dalam rentang minValue..maxValue sebelum Anda menetapkannya ke properti fieldData:
class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}
  1. Di class SmartTvDevice, gunakan class delegasi untuk menentukan properti speakerVolume dan channelNumber:
class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    ...

}
  1. Di class SmartLightDevice, gunakan class delegasi untuk menentukan properti brightnessLevel:
class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    ...

}

10. Menguji solusi

Anda dapat melihat kode solusi dalam cuplikan kode ini:

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

open class SmartDevice(val name: String, val category: String) {

    var deviceStatus = "online"
        protected set

    open val deviceType = "unknown"

    open fun turnOn() {
        deviceStatus = "on"
    }

    open fun turnOff() {
        deviceStatus = "off"
    }
}

class SmartTvDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart TV"

    private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)

    private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)

    fun increaseSpeakerVolume() {
        speakerVolume++
        println("Speaker volume increased to $speakerVolume.")
    }

    fun nextChannel() {
        channelNumber++
        println("Channel number increased to $channelNumber.")
    }

    override fun turnOn() {
        super.turnOn()
        println(
            "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " +
                "set to $channelNumber."
        )
    }

    override fun turnOff() {
        super.turnOff()
        println("$name turned off")
    }
}

class SmartLightDevice(deviceName: String, deviceCategory: String) :
    SmartDevice(name = deviceName, category = deviceCategory) {

    override val deviceType = "Smart Light"

    private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)

    fun increaseBrightness() {
        brightnessLevel++
        println("Brightness increased to $brightnessLevel.")
    }

    override fun turnOn() {
        super.turnOn()
        brightnessLevel = 2
        println("$name turned on. The brightness level is $brightnessLevel.")
    }

    override fun turnOff() {
        super.turnOff()
        brightnessLevel = 0
        println("Smart Light turned off")
    }
}

class SmartHome(
    val smartTvDevice: SmartTvDevice,
    val smartLightDevice: SmartLightDevice
) {

    var deviceTurnOnCount = 0
        private set

    fun turnOnTv() {
        deviceTurnOnCount++
        smartTvDevice.turnOn()
    }

    fun turnOffTv() {
        deviceTurnOnCount--
        smartTvDevice.turnOff()
    }

    fun increaseTvVolume() {
        smartTvDevice.increaseSpeakerVolume()
    }

    fun changeTvChannelToNext() {
        smartTvDevice.nextChannel()
    }

    fun turnOnLight() {
        deviceTurnOnCount++
        smartLightDevice.turnOn()
    }

    fun turnOffLight() {
        deviceTurnOnCount--
        smartLightDevice.turnOff()
    }

    fun increaseLightBrightness() {
        smartLightDevice.increaseBrightness()
    }

    fun turnOffAllDevices() {
        turnOffTv()
        turnOffLight()
    }
}

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {

    var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        }
    }
}

fun main() {
    var smartDevice: SmartDevice = SmartTvDevice("Android TV", "Entertainment")
    smartDevice.turnOn()

    smartDevice = SmartLightDevice("Google Light", "Utility")
    smartDevice.turnOn()
}

Outputnya adalah sebagai berikut:

Android TV is turned on. Speaker volume is set to 2 and channel number is set to 1.
Google Light turned on. The brightness level is 2.

11. Coba tantangan ini

  • Di class SmartDevice, tentukan metode printDeviceInfo() yang mencetak string "Device name: $name, category: $category, type: $deviceType".
  • Di class SmartTvDevice, tentukan metode decreaseVolume() yang menurunkan volume dan metode previousChannel() yang membuka saluran sebelumnya.
  • Di class SmartLightDevice, tentukan metode decreaseBrightness() yang mengurangi kecerahan.
  • Di class SmartHome, pastikan bahwa semua tindakan hanya dapat dilakukan jika properti deviceStatus setiap perangkat disetel ke string "on". Selain itu, pastikan properti deviceTurnOnCount diupdate dengan benar.

Setelah Anda selesai mengimplementasikan:

  • Di class SmartHome, tentukan metode decreaseTvVolume(), changeTvChannelToPrevious(), printSmartTvInfo(), printSmartLightInfo(), dan decreaseLightBrightness().
  • Panggil metode yang sesuai dari class SmartTvDevice dan SmartLightDevice di class SmartHome.
  • Dalam fungsi main(), panggil metode yang ditambahkan ini untuk mengujinya.

12. Kesimpulan

Selamat! Anda telah mempelajari cara menentukan class dan membuat instance objek. Anda juga telah mempelajari cara membuat hubungan antar-class dan membuat delegasi properti.

Ringkasan

  • Ada empat prinsip utama OOP: enkapsulasi, abstraksi, pewarisan, dan polimorfisme.
  • Class ditentukan dengan kata kunci class, serta berisi properti dan metode.
  • Properti serupa dengan variabel, kecuali properti dapat memiliki pengambil dan penyetel kustom.
  • Konstruktor menentukan cara membuat instance objek class.
  • Anda dapat menghilangkan kata kunci constructor saat menentukan konstruktor utama.
  • Pewarisan mempermudah penggunaan kembali kode.
  • Hubungan IS-A mengacu pada pewarisan.
  • Hubungan HAS-A mengacu pada komposisi.
  • Pengubah visibilitas memainkan peran penting dalam pencapaian enkapsulasi.
  • Kotlin menyediakan empat pengubah visibilitas: pengubah public, private, protected, dan internal.
  • Delegasi properti memungkinkan Anda menggunakan kembali kode pengambil dan penyetel di beberapa class.

Pelajari lebih lanjut