1. Pengantar
Selama beberapa dekade, programmer merancang beberapa fitur bahasa pemrograman untuk membantu Anda menulis kode yang lebih baik—menunjukkan ide yang sama dengan lebih sedikit kode, abstraksi untuk mengekspresikan ide yang kompleks, dan menulis kode yang mencegah developer lain membuat kesalahan secara tidak sengaja hanyalah beberapa contoh. Begitu pula dengan bahasa Kotlin, dan ada sejumlah fitur yang dimaksudkan untuk membantu developer menulis kode yang lebih ekspresif.
Sayangnya, fitur-fitur ini dapat membuat segalanya menjadi rumit jika ini adalah pemrograman pertama Anda. Meskipun mungkin terdengar berguna, tingkat kegunaannya dan masalah yang dapat dipecahkan mungkin tidak selalu jelas. Kemungkinan besar, Anda sudah melihat beberapa fitur yang digunakan di Compose dan library lainnya.
Meskipun tidak ada pengganti untuk pengalaman, codelab ini menampilkan beberapa konsep Kotlin yang dapat membantu Anda membuat struktur aplikasi yang lebih besar:
- Generik
- Berbagai jenis class (class enum dan class data)
- Objek pendamping dan singleton
- Fungsi dan properti ekstensi
- Fungsi cakupan
Di akhir codelab ini, Anda akan memiliki pengetahuan yang lebih mendalam tentang kode yang telah Anda lihat di kursus ini, dan mempelajari beberapa contoh kapan Anda akan menemukan atau menggunakan konsep ini di aplikasi Anda sendiri.
Prasyarat
- Pemahaman tentang konsep pemrograman berorientasi objek, termasuk pewarisan.
- Cara menentukan dan mengimplementasikan antarmuka.
Yang akan Anda pelajari
- Cara menentukan parameter jenis generik untuk class.
- Cara membuat instance class generik.
- Kapan class enum dan data digunakan.
- Cara menentukan parameter jenis generik yang harus mengimplementasikan antarmuka.
- Cara menggunakan fungsi cakupan untuk mengakses metode dan properti class.
- Cara menentukan objek singleton dan objek pendamping untuk class.
- Cara memperluas class yang ada dengan properti dan metode baru.
Yang akan Anda butuhkan
- Browser web dengan akses ke Kotlin Playground.
2. Membuat class yang dapat digunakan kembali dengan generik
Misalnya, Anda sedang menulis aplikasi untuk kuis online, serupa dengan kuis yang Anda lihat di kursus ini. Sering kali ada beberapa jenis pertanyaan kuis, seperti mengisi titik-titik, atau benar atau salah. Setiap pertanyaan kuis dapat diwakili oleh class, dengan beberapa properti.
Teks pertanyaan dalam kuis dapat diwakili oleh string. Pertanyaan kuis juga perlu menampilkan jawabannya. Namun, jenis pertanyaan yang berbeda—seperti benar atau salah—mungkin perlu merepresentasikan jawaban menggunakan jenis data yang berbeda. Mari kita tentukan tiga jenis pertanyaan yang berbeda.
- Pertanyaan mengisi titik-titik: Jawabannya adalah kata yang direpresentasikan oleh
String
. - Pertanyaan benar atau salah: Jawaban direpresentasikan oleh
Boolean
. - Soal matematika: Jawabannya adalah nilai numerik. Jawaban untuk soal aritmetika sederhana direpresentasikan oleh
Int
.
Selain itu, terlepas dari jenis pertanyaannya, pertanyaan kuis dalam contoh kami juga memiliki rating kesulitan. Rating kesulitan direpresentasikan oleh string dengan tiga kemungkinan nilai: "easy"
, "medium"
, atau "hard"
.
Menentukan class untuk merepresentasikan setiap jenis pertanyaan kuis:
- Buka Kotlin Playground.
- Di atas fungsi
main()
, tentukan class untuk pertanyaan mengisi titik-titik bernamaFillInTheBlankQuestion
, yang terdiri dari propertiString
untukquestionText
, propertiString
untukanswer
, dan propertiString
untukdifficulty
.
class FillInTheBlankQuestion(
val questionText: String,
val answer: String,
val difficulty: String
)
- Di bawah class
FillInTheBlankQuestion
, tentukan class lain bernamaTrueOrFalseQuestion
untuk pertanyaan benar atau salah, yang terdiri dari propertiString
untukquestionText
, propertiBoolean
untukanswer
, dan propertiString
untukdifficulty
.
class TrueOrFalseQuestion(
val questionText: String,
val answer: Boolean,
val difficulty: String
)
- Terakhir, di bawah dua class lainnya, tentukan class
NumericQuestion
yang terdiri dari propertiString
untukquestionText
, propertiInt
untukanswer
, dan propertiString
untukdifficulty
.
class NumericQuestion(
val questionText: String,
val answer: Int,
val difficulty: String
)
- Lihat kode yang Anda tulis. Apakah Anda melihat pengulangan?
class FillInTheBlankQuestion(
val questionText: String,
val answer: String,
val difficulty: String
)
class TrueOrFalseQuestion(
val questionText: String,
val answer: Boolean,
val difficulty: String
)
class NumericQuestion(
val questionText: String,
val answer: Int,
val difficulty: String
)
Ketiga class tersebut memiliki properti yang sama persis: questionText
, answer
, dan difficulty
. Satu-satunya perbedaan adalah jenis data properti answer
. Anda mungkin berpikir bahwa solusi yang jelas adalah membuat class induk dengan questionText
dan difficulty
, dan setiap subclass menentukan properti answer
.
Namun, menggunakan pewarisan memiliki masalah yang sama seperti di atas. Setiap kali menambahkan jenis pertanyaan baru, Anda harus menambahkan properti answer
. Satu-satunya perbedaan adalah jenis data. Memiliki class induk Question
yang tidak memiliki properti jawaban juga akan terlihat aneh.
Jika Anda ingin properti memiliki jenis data yang berbeda, pembuatan subclass bukanlah jawabannya. Sebaliknya, Kotlin menyediakan sesuatu yang disebut jenis generik, yang memungkinkan Anda memiliki satu properti yang dapat memiliki jenis data berbeda, bergantung pada kasus penggunaan tertentu.
Apa yang dimaksud dengan jenis data generik?
Jenis umum, atau disingkat generik, memungkinkan jenis data, seperti class, untuk menentukan jenis data placeholder yang tidak diketahui dan dapat digunakan dengan properti dan metodenya. Apa maksudnya?
Pada contoh di atas, alih-alih menentukan properti jawaban untuk setiap kemungkinan jenis data, Anda dapat membuat satu class untuk merepresentasikan pertanyaan apa pun, dan menggunakan nama placeholder untuk jenis data properti answer
. Jenis data sebenarnya—String
, Int
, Boolean
, dll.—ditentukan saat instance class tersebut dibuat. Di mana pun nama placeholder digunakan, jenis data yang diteruskan ke class akan digunakan. Sintaksis untuk menentukan jenis generik suatu class ditampilkan di bawah ini:
Jenis data generik disediakan saat membuat instance class, sehingga perlu ditetapkan sebagai bagian dari tanda tangan class. Nama class harus diikuti tanda kurung sudut yang menghadap ke kiri (<
), diikuti dengan nama placeholder untuk jenis data, diikuti dengan tanda kurung sudut yang menghadap ke kanan (>
).
Nama placeholder kemudian dapat digunakan di mana pun Anda menggunakan jenis data sebenarnya dalam class, seperti untuk properti.
Nama ini sama dengan deklarasi properti lainnya, tetapi yang digunakan adalah nama placeholder, bukan jenis data.
Bagaimana class Anda pada akhirnya akan mengetahui jenis data mana yang akan digunakan? Jenis data yang digunakan oleh jenis generik akan diteruskan sebagai parameter dalam tanda kurung siku saat Anda membuat instance class.
Nama class harus diikuti dengan tanda kurung sudut yang menghadap ke kiri (<
), lalu jenis data sebenarnya seperti String
, Boolean
, Int
, dan lainnya, kemudian tanda kurung sudut yang menghadap ke kanan (>
). Jenis data nilai yang Anda teruskan untuk properti generik harus cocok dengan jenis data dalam tanda kurung sudut. Anda akan membuat properti jawaban generik agar dapat menggunakan satu class untuk merepresentasikan semua jenis pertanyaan kuis, baik jawabannya adalah String
, Boolean
, Int
, maupun jenis data arbitrer.
Memfaktorkan ulang kode untuk menggunakan generik
Faktorkan ulang kode Anda untuk menggunakan satu class bernama Question
dengan properti jawaban generik.
- Hapus definisi class untuk
FillInTheBlankQuestion
,TrueOrFalseQuestion
, danNumericQuestion
. - Buat class baru bernama
Question
.
class Question()
- Setelah nama class, tetapi sebelum tanda kurung, tambahkan parameter jenis generik menggunakan tanda kurung sudut yang menghadap ke kiri dan ke kanan. Panggil jenis generik
T
.
class Question<T>()
- Tambahkan properti
questionText
,answer
, dandifficulty
.questionText
harus berjenisString
.answer
harus berjenisT
karena jenis datanya ditentukan saat membuat instance classQuestion
. Propertidifficulty
harus berjenisString
.
class Question<T>(
val questionText: String,
val answer: T,
val difficulty: String
)
- Untuk melihat cara kerjanya dengan beberapa jenis pertanyaan—mengisi titik-titik, benar atau salah, dll.—buat tiga instance class
Question
dimain()
, seperti yang ditunjukkan di bawah.
fun main() {
val question1 = Question<String>("Quoth the raven ___", "nevermore", "medium")
val question2 = Question<Boolean>("The sky is green. True or false", false, "easy")
val question3 = Question<Int>("How many days are there between full moons?", 28, "hard")
}
- Jalankan kode Anda untuk memastikan semua berfungsi dengan benar. Sekarang Anda memiliki tiga instance dari class
Question
—masing-masing dengan jenis data berbeda untuk jawaban—bukan tiga class berbeda, atau tidak menggunakan pewarisan. Jika ingin menangani pertanyaan dengan jenis jawaban yang berbeda, Anda dapat menggunakan kembali classQuestion
yang sama.
3. Menggunakan class enum
Di bagian sebelumnya, Anda sudah menentukan properti tingkat kesulitan dengan tiga kemungkinan nilai: "mudah", "sedang", dan "sulit". Meskipun cara ini berhasil, ada beberapa masalah.
- Jika Anda tidak sengaja salah mengetik salah satu dari tiga kemungkinan string, Anda dapat memunculkan bug.
- Jika nilainya berubah, misalnya
"medium"
diganti namanya menjadi"average"
, Anda perlu mengupdate semua penggunaan string. - Tidak ada yang mencegah Anda atau developer lain secara tidak sengaja menggunakan string lain yang bukan salah satu dari tiga nilai yang valid.
- Kode akan lebih sulit dipertahankan jika Anda menambahkan lebih banyak tingkat kesulitan.
Kotlin membantu Anda mengatasi masalah ini menggunakan jenis class khusus yang disebut class enum. Class enum digunakan untuk membuat jenis dengan serangkaian kemungkinan nilai yang terbatas. Di dunia nyata, misalnya, empat arah mata angin—utara, selatan, timur, dan barat—dapat direpresentasikan oleh class enum. Penggunaan arah tambahan tidak diperlukan, dan kode tidak akan memungkinkan penggunaan itu. Sintaksis untuk class enum ditampilkan di bawah ini.
Setiap kemungkinan nilai enum disebut konstanta enum. Konstanta enum ditempatkan di dalam tanda kurung kurawal yang dipisahkan oleh koma. Konvensinya adalah menggunakan huruf besar untuk setiap huruf dalam nama konstanta.
Anda mengacu pada konstanta enum menggunakan operator titik.
Menggunakan konstanta enum
Ubah kode Anda agar menggunakan konstanta enum, bukan String
, untuk merepresentasikan tingkat kesulitan.
- Di bawah class
Question
, tentukan classenum
yang disebutDifficulty
.
enum class Difficulty {
EASY, MEDIUM, HARD
}
- Di class
Question
, ubah jenis data propertidifficulty
dariString
menjadiDifficulty
.
class Question<T>(
val questionText: String,
val answer: T,
val difficulty: Difficulty
)
- Saat melakukan inisialisasi tiga pertanyaan, teruskan konstanta enum untuk tingkat kesulitan.
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
4. Menggunakan class data
Banyak class yang telah Anda gunakan sejauh ini, seperti subclass Activity
, memiliki beberapa metode untuk melakukan berbagai tindakan. Class ini tidak hanya merepresentasikan data, tetapi juga berisi banyak fungsi.
Di sisi lain, class seperti class Question
hanya berisi data. Tidak ada metode apa pun yang melakukan tindakan. Class ini dapat ditetapkan sebagai class data. Menentukan class sebagai class data memungkinkan compiler Kotlin membuat asumsi tertentu dan menerapkan beberapa metode secara otomatis. Misalnya, toString()
dipanggil di balik layar oleh fungsi println()
. Saat Anda menggunakan class data, toString()
dan metode lainnya akan diimplementasikan secara otomatis berdasarkan properti class.
Untuk menentukan class data, cukup tambahkan kata kunci data
sebelum kata kunci class
.
Mengonversi Question
ke class data
Pertama, Anda akan melihat apa yang terjadi saat mencoba memanggil metode seperti toString()
pada class yang bukan merupakan class data. Kemudian, Anda akan mengonversi Question
menjadi class data sehingga metode ini dan metode lainnya akan diimplementasikan secara default.
- Di
main()
, cetak hasil panggilantoString()
diquestion1
.
fun main() {
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
println(question1.toString())
}
- Jalankan kode. Output hanya menampilkan nama class dan ID unik untuk objek.
Question@37f8bb67
- Buat
Question
menjadi class data menggunakan kata kuncidata
.
data class Question<T>(
val questionText: String,
val answer: T,
val difficulty: Difficulty
)
- Jalankan lagi kode. Dengan menandai ini sebagai class data, Kotlin dapat menentukan cara menampilkan properti class saat memanggil
toString()
.
Question(questionText=Quoth the raven ___, answer=nevermore, difficulty=MEDIUM)
Jika class didefinisikan sebagai class data, metode berikut akan diimplementasikan.
equals()
hashCode()
: Anda akan melihat metode ini saat menangani jenis koleksi tertentu.toString()
componentN()
:component1()
,component2()
, dll.copy()
5. Menggunakan objek singleton
Ada banyak skenario ketika Anda ingin class hanya memiliki satu instance. Contoh:
- Statistik pemain dalam game seluler untuk pengguna saat ini.
- Berinteraksi dengan satu perangkat hardware, seperti mengirim audio melalui speaker.
- Objek untuk mengakses sumber data jarak jauh (seperti database Firebase).
- Autentikasi, dengan hanya satu pengguna yang dapat login dalam satu waktu.
Dalam skenario di atas, Anda mungkin perlu menggunakan class. Namun, Anda hanya perlu membuat satu instance dari class tersebut. Jika hanya ada satu perangkat hardware, atau hanya satu pengguna yang login dalam satu waktu, tidak akan ada alasan untuk membuat lebih dari satu instance. Memiliki dua objek yang mengakses perangkat hardware yang sama secara bersamaan dapat menyebabkan perilaku yang sangat aneh dan mengganggu.
Anda dapat menyampaikan dengan jelas dalam kode Anda bahwa objek hanya boleh memiliki satu instance dengan menentukannya sebagai singleton. Singleton adalah class yang hanya dapat memiliki satu instance. Kotlin menyediakan konstruksi khusus, yang disebut objek, yang dapat digunakan untuk membuat class singleton.
Menentukan objek singleton
Sintaksis objek serupa dengan sintaksis class. Cukup gunakan kata kunci object
, bukan kata kunci class
. Objek singleton tidak dapat memiliki konstruktor karena Anda tidak dapat membuat instance secara langsung. Sebagai gantinya, semua properti ditentukan dalam tanda kurung kurawal dan diberi nilai awal.
Beberapa contoh yang diberikan sebelumnya mungkin tidak terlihat jelas, terutama jika Anda belum menangani perangkat hardware tertentu atau menangani autentikasi dalam aplikasi Anda. Namun, Anda akan melihat objek singleton muncul ketika Anda terus mempelajari pengembangan Android. Mari kita lihat cara kerjanya dengan contoh sederhana menggunakan objek untuk status pengguna, yang hanya memerlukan satu instance.
Untuk kuis, sebaiknya Anda memiliki cara untuk memantau jumlah total pertanyaan, dan jumlah pertanyaan yang dijawab siswa sejauh ini. Anda hanya perlu satu instance class ini, jadi alih-alih mendeklarasikannya sebagai class, deklarasikan sebagai objek singleton.
- Buat objek bernama
StudentProgress
.
object StudentProgress {
}
- Untuk contoh ini, kami akan mengasumsikan ada total sepuluh pertanyaan, dan tiga di antaranya telah dijawab sejauh ini. Tambahkan dua properti
Int
:total
dengan nilai10
, dananswered
dengan nilai3
.
object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
Mengakses objek singleton
Ingat bahwa Anda tidak dapat membuat instance objek singleton secara langsung? Lalu, bagaimana cara Anda mengakses propertinya?
Karena hanya ada satu instance StudentProgress
pada satu waktu, Anda mengakses propertinya dengan merujuk ke nama objek itu sendiri, diikuti dengan operator titik (.
), lalu nama properti.
Update fungsi main()
untuk mengakses properti objek singleton.
- Di
main()
, tambahkan panggilan keprintln()
yang menghasilkan pertanyaananswered
dantotal
dari objekStudentProgress
.
fun main() {
...
println("${StudentProgress.answered} of ${StudentProgress.total} answered.")
}
- Jalankan kode untuk memastikan semua berfungsi.
... 3 of 10 answered.
Mendeklarasikan objek sebagai objek pendamping
Class dan objek di Kotlin dapat ditentukan di dalam jenis lain, dan dapat menjadi cara yang bagus untuk mengatur kode Anda. Anda dapat menentukan objek singleton di dalam class lain menggunakan objek pendamping. Objek pendamping memungkinkan Anda mengakses properti dan metodenya dari dalam class, jika properti dan metode objek adalah bagian dari class tersebut, memungkinkan sintaksis yang lebih ringkas.
Untuk mendeklarasikan objek pendamping, cukup tambahkan kata kunci companion
sebelum kata kunci object
.
Anda akan membuat class baru bernama Quiz
untuk menyimpan pertanyaan kuis dan menjadikan StudentProgress
sebagai objek pendamping class Quiz
.
- Di bawah enum
Difficulty
, tentukan class baru bernamaQuiz
.
class Quiz {
}
- Pindahkan
question1
,question2
, danquestion3
darimain()
ke classQuiz
. Anda juga harus menghapusprintln(question1.toString())
jika Anda belum melakukannya.
class Quiz {
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
}
- Pindahkan objek
StudentProgress
ke dalam classQuiz
.
class Quiz {
val question1 = Question<String>("Quoth the raven ___", "nevermore", Difficulty.MEDIUM)
val question2 = Question<Boolean>("The sky is green. True or false", false, Difficulty.EASY)
val question3 = Question<Int>("How many days are there between full moons?", 28, Difficulty.HARD)
object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
}
- Tandai objek
StudentProgress
dengan kata kuncicompanion
.
companion object StudentProgress {
var total: Int = 10
var answered: Int = 3
}
- Update panggilan ke
println()
untuk mereferensikan properti denganQuiz.answered
danQuiz.total
. Meskipun properti ini dideklarasikan dalam objekStudentProgress
, properti ini dapat diakses dengan notasi titik menggunakan nama classQuiz
saja.
fun main() {
println("${Quiz.answered} of ${Quiz.total} answered.")
}
- Jalankan kode untuk memverifikasi output.
3 of 10 answered.
6. Memperluas class dengan properti dan metode baru
Saat menangani Compose, Anda mungkin melihat beberapa sintaksis yang menarik saat menentukan ukuran elemen UI. Jenis numerik, seperti Double
, tampaknya memiliki properti seperti dp
dan sp
yang menentukan dimensi.
Mengapa desainer bahasa Kotlin menyertakan properti dan fungsi pada jenis data bawaan, terutama untuk membangun UI Android? Apakah mereka mampu memprediksi masa depan? Apakah Kotlin dirancang untuk digunakan dengan Compose, bahkan sebelum Compose ada?
Tentu saja tidak! Saat menulis class, Anda sering tidak tahu persis bagaimana developer lain akan menggunakannya, atau berencana untuk menggunakannya, di aplikasi mereka. Tidak mungkin memprediksi semua kasus penggunaan di masa mendatang, dan juga tidak disarankan untuk menambahkan penggelembungan yang tidak perlu ke kode Anda untuk beberapa kasus penggunaan yang tidak terduga.
Fungsi bahasa Kotlin adalah memberi developer lain kemampuan untuk memperluas jenis data yang ada, menambahkan properti dan metode yang dapat diakses dengan sintaksis titik, seolah-olah merupakan bagian dari jenis data tersebut. Developer yang tidak menangani jenis floating point di Kotlin, misalnya seseorang yang membangun library Compose, dapat memilih untuk menambahkan properti dan metode khusus ke dimensi UI.
Karena Anda telah melihat sintaksis ini saat mempelajari Compose di dua unit pertama, kini saatnya Anda mempelajari cara kerjanya di balik layar. Anda akan menambahkan beberapa properti dan metode untuk memperluas jenis yang ada.
Menambahkan properti ekstensi
Untuk menentukan properti ekstensi, tambahkan nama jenis dan operator titik (.
) sebelum nama variabel.
Anda akan memfaktorkan ulang kode di fungsi main() untuk mencetak progres kuis dengan properti ekstensi.
- Di bawah class
Quiz
, tentukan properti ekstensiQuiz.StudentProgress
yang bernamaprogressText
dari jenisString
.
val Quiz.StudentProgress.progressText: String
- Tentukan pengambil untuk properti ekstensi yang menampilkan string yang sama dengan yang digunakan sebelumnya di
main()
.
val Quiz.StudentProgress.progressText: String
get() = "${answered} of ${total} answered"
- Ganti kode dalam fungsi
main()
dengan kode yang mencetakprogressText
. Karena properti ini merupakan properti ekstensi objek pendamping, Anda dapat mengaksesnya dengan notasi titik menggunakan nama class,Quiz
.
fun main() {
println(Quiz.progressText)
}
- Jalankan kode untuk memverifikasi bahwa kode berfungsi.
3 of 10 answered.
Menambahkan fungsi ekstensi
Untuk menentukan fungsi ekstensi, tambahkan nama jenis dan operator titik (.
) sebelum nama fungsi.
Anda akan menambahkan fungsi ekstensi untuk menampilkan progres kuis sebagai status progres. Karena Anda tidak dapat membuat status progres di playground Kotlin, Anda akan mencetak status progres bergaya retro menggunakan teks.
- Tambahkan fungsi ekstensi ke objek
StudentProgress
bernamaprintProgressBar()
. Fungsi ini tidak boleh menggunakan parameter dan tidak memiliki nilai return.
fun Quiz.StudentProgress.printProgressBar() {
}
- Cetak karakter
▓
,answered
kali, menggunakanrepeat()
. Bagian status progres yang diarsir gelap merepresentasikan jumlah pertanyaan yang dijawab. Gunakanprint()
karena Anda tidak ingin baris baru setelah setiap karakter.
fun Quiz.StudentProgress.printProgressBar() {
repeat(Quiz.answered) { print("▓") }
}
- Cetak karakter
▒
, berapa kali yang sama dengan perbedaan antaratotal
dananswered
, menggunakanrepeat()
. Bagian yang diarsir terang merepresentasikan pertanyaan yang tersisa di panel proses.
fun Quiz.StudentProgress.printProgressBar() {
repeat(Quiz.answered) { print("▓") }
repeat(Quiz.total - Quiz.answered) { print("▒") }
}
- Cetak baris baru menggunakan
println()
tanpa argumen, lalu cetakprogressText
.
fun Quiz.StudentProgress.printProgressBar() {
repeat(Quiz.answered) { print("▓") }
repeat(Quiz.total - Quiz.answered) { print("▒") }
println()
println(Quiz.progressText)
}
- Update kode di
main()
untuk memanggilprintProgressBar()
.
fun main() {
Quiz.printProgressBar()
}
- Jalankan kode untuk memverifikasi output.
▓▓▓▒▒▒▒▒▒▒ 3 of 10 answered.
Apakah Anda wajib melakukannya? Tentu saja tidak. Namun, memiliki opsi properti dan metode ekstensi akan memberi Anda lebih banyak opsi untuk mengekspos kode Anda kepada developer lain. Menggunakan sintaksis titik di jenis lain dapat membuat kode Anda lebih mudah dibaca, baik untuk Anda sendiri maupun developer lain.
7. Menulis ulang fungsi ekstensi menggunakan antarmuka
Di halaman sebelumnya, Anda telah melihat cara menambahkan properti dan metode ke objek StudentProgress
tanpa menambahkan kode ke dalamnya secara langsung, menggunakan properti ekstensi dan fungsi ekstensi. Meskipun cara ini efektif untuk menambahkan fungsi ke satu class yang sudah ditentukan, terkadang Anda tidak perlu memperluas class jika memiliki akses ke kode sumber. Ada juga situasi saat Anda tidak tahu apa yang harus diimplementasikan, hanya metode atau properti tertentu yang harus ada. Jika Anda memerlukan beberapa class untuk memiliki properti dan metode tambahan yang sama, mungkin dengan perilaku yang berbeda, Anda dapat menentukan properti dan metode ini dengan antarmuka.
Misalnya, selain kuis, Anda juga memiliki class untuk survei, langkah dalam resep, atau data lain yang diurutkan yang dapat menggunakan status progres. Anda dapat menentukan antarmuka yang menetapkan metode dan/atau properti yang harus disertakan oleh setiap class ini.
Antarmuka ditentukan menggunakan kata kunci interface
, diikuti dengan nama dalam bentuk UpperCamelCase, lalu kurung kurawal buka dan tutup. Dalam tanda kurung kurawal, Anda dapat menentukan tanda tangan metode atau properti get-only yang harus diimplementasikan oleh setiap class yang sesuai dengan antarmuka.
Antarmuka adalah kontrak. Class yang sesuai dengan antarmuka dikatakan memperluas antarmuka. Class dapat mendeklarasikan bahwa class tersebut ingin memperluas antarmuka menggunakan titik dua (:
), diikuti dengan spasi, lalu nama antarmuka.
Sebagai hasilnya, class harus mengimplementasikan semua properti dan metode yang ditetapkan dalam antarmuka. Hal ini memungkinkan Anda dengan mudah memastikan bahwa setiap class yang perlu memperluas antarmuka akan mengimplementasikan metode yang sama persis dengan tanda tangan metode yang sama persis. Jika Anda memodifikasi antarmuka dengan cara apa pun, seperti menambahkan atau menghapus properti atau metode, atau mengubah tanda tangan metode, compiler mengharuskan Anda mengupdate class yang memperluas antarmuka, sehingga kode Anda tetap konsisten dan lebih mudah dikelola.
Antarmuka memungkinkan variasi perilaku class yang memperluasnya. Implementasi bergantung pada setiap class.
Mari kita lihat bagaimana Anda dapat menulis ulang status progres untuk menggunakan antarmuka, dan membuat class Kuis memperluas antarmuka tersebut.
- Di atas class
Quiz
, tentukan antarmuka bernamaProgressPrintable
. Kami telah memilih namaProgressPrintable
karena membuat class yang memperluasnya dapat mencetak status progres.
interface ProgressPrintable {
}
- Di antarmuka
ProgressPrintable
, tentukan properti bernamaprogressText
.
interface ProgressPrintable {
val progressText: String
}
- Ubah deklarasi class
Quiz
untuk memperluas antarmukaProgressPrintable
.
class Quiz : ProgressPrintable {
...
}
- Di class
Quiz
, tambahkan properti bernamaprogressText
dari jenisString
, seperti yang ditentukan dalam antarmukaProgressPrintable
. Karena properti berasal dariProgressPrintable
, awalival
dengan kata kunci penggantian.
override val progressText: String
- Salin pengambil properti dari properti ekstensi
progressText
lama.
override val progressText: String
get() = "${answered} of ${total} answered"
- Hapus properti ekstensi
progressText
lama.
Kode yang akan dihapus:
val Quiz.StudentProgress.progressText: String
get() = "${answered} of ${total} answered"
- Di antarmuka
ProgressPrintable
, tambahkan metode bernamaprintProgressBar
yang tidak menggunakan parameter dan tidak memiliki nilai yang ditampilkan.
interface ProgressPrintable {
val progressText: String
fun printProgressBar()
}
- Di class
Quiz
, tambahkan metodeprintProgressBar()
menggunakan kata kuncioverride
.
override fun printProgressBar() {
}
- Pindahkan kode dari fungsi ekstensi
printProgressBar()
lama keprintProgressBar()
baru dari antarmuka. Ubah baris terakhir untuk merujuk ke variabelprogressText
baru dari antarmuka dengan menghapus referensi keQuiz
.
override fun printProgressBar() {
repeat(Quiz.answered) { print("▓") }
repeat(Quiz.total - Quiz.answered) { print("▒") }
println()
println(progressText)
}
- Hapus fungsi ekstensi
printProgressBar()
. Fungsi ini sekarang milik classQuiz
yang memperluasProgressPrintable
.
Kode yang akan dihapus:
fun Quiz.StudentProgress.printProgressBar() {
repeat(Quiz.answered) { print("▓") }
repeat(Quiz.total - Quiz.answered) { print("▒") }
println()
println(Quiz.progressText)
}
- Perbarui kode di
main()
. Karena fungsiprintProgressBar()
kini merupakan metode classQuiz
, Anda harus membuat instance objekQuiz
terlebih dahulu, lalu memanggilprintProgressBar()
.
fun main() {
Quiz().printProgressBar()
}
- Jalankan kode. Outputnya tidak berubah, tetapi kode Anda sekarang lebih modular. Seiring berkembangnya codebase, Anda dapat dengan mudah menambahkan class yang sesuai dengan antarmuka yang sama untuk menggunakan kembali kode tanpa mewarisi dari superclass.
▓▓▓▒▒▒▒▒▒▒ 3 of 10 answered.
Ada banyak kasus penggunaan antarmuka untuk membantu menyusun kode dan Anda akan mulai sering menggunakannya di unit umum. Berikut adalah beberapa contoh antarmuka yang mungkin Anda temui saat terus menggunakan Kotlin.
- Injeksi dependensi manual. Buat antarmuka yang menentukan semua properti dan metode dependensi. Mewajibkan antarmuka sebagai jenis data dependensi (aktivitas, kasus pengujian, dll.) sehingga instance dari setiap class yang mengimplementasikan antarmuka dapat digunakan. Hal ini memungkinkan Anda menukar implementasi yang mendasarinya.
- Tiruan untuk pengujian otomatis. Baik class tiruan maupun class asli sesuai dengan antarmuka yang sama.
- Mengakses dependensi yang sama di aplikasi Compose Multiplatform. Misalnya, buat antarmuka yang menyediakan serangkaian properti dan metode umum untuk Android dan desktop, meskipun implementasi yang mendasarinya berbeda untuk setiap platform.
- Beberapa jenis data di Compose, seperti
Modifier
, adalah antarmuka. Hal ini memungkinkan Anda menambahkan pengubah baru tanpa perlu mengakses atau mengubah kode sumber yang mendasarinya.
8. Menggunakan fungsi cakupan untuk mengakses properti dan metode class
Seperti yang sudah Anda lihat, Kotlin menyertakan banyak fitur untuk membuat kode lebih ringkas.
Salah satu fitur yang akan Anda temui ketika Anda terus mempelajari pengembangan Android adalah fungsi cakupan. Fungsi cakupan memungkinkan Anda mengakses properti dan metode dari class secara singkat tanpa harus berulang kali mengakses nama variabel. Apa maksudnya? Mari kita lihat contoh berikut.
Menghilangkan referensi objek berulang dengan fungsi cakupan
Fungsi cakupan adalah fungsi tingkat tinggi yang memungkinkan Anda mengakses properti dan metode objek tanpa merujuk pada nama objek. Ini disebut fungsi cakupan karena isi fungsi yang diteruskan mengambil cakupan objek yang digunakan untuk memanggil fungsi cakupan. Misalnya, beberapa fungsi cakupan memungkinkan Anda mengakses properti dan metode dalam sebuah class, seolah-olah fungsi tersebut didefinisikan sebagai metode class tersebut. Hal ini dapat membuat kode Anda lebih mudah dibaca dengan memungkinkan Anda menghapus nama objek saat menyertakannya berlebihan.
Untuk menggambarkan hal ini dengan lebih baik, mari kita lihat beberapa fungsi cakupan berbeda yang akan Anda temui nanti dalam kursus.
Ganti nama objek panjang menggunakan let()
Fungsi let()
memungkinkan Anda merujuk ke objek dalam ekspresi lambda menggunakan ID it
, bukan nama objek yang sebenarnya. Hal ini dapat membantu Anda menghindari penggunaan nama objek yang lebih deskriptif dan panjang berulang kali ketika mengakses lebih dari satu properti. Fungsi let()
adalah fungsi ekstensi yang dapat dipanggil pada objek Kotlin apa pun menggunakan notasi titik.
Coba akses properti question1
, question2
, dan question3
menggunakan let()
:
- Tambahkan fungsi ke class
Quiz
yang bernamaprintQuiz()
.
fun printQuiz() {
}
- Tambahkan kode berikut yang mencetak
questionText
,answer
, dandifficulty
pertanyaan. Meskipun beberapa properti diakses untukquestion1
,question2
, danquestion3
, seluruh nama variabel digunakan setiap waktu. Jika nama variabel berubah, Anda perlu mengupdate setiap penggunaan.
fun printQuiz() {
println(question1.questionText)
println(question1.answer)
println(question1.difficulty)
println()
println(question2.questionText)
println(question2.answer)
println(question2.difficulty)
println()
println(question3.questionText)
println(question3.answer)
println(question3.difficulty)
println()
}
- Masukkan kode yang mengakses properti
questionText
,answer
, dandifficulty
dengan panggilan ke fungsilet()
diquestion1
,question2
, danquestion3
. Ganti nama variabel di setiap ekspresi lambda dengannya.
fun printQuiz() {
question1.let {
println(it.questionText)
println(it.answer)
println(it.difficulty)
}
println()
question2.let {
println(it.questionText)
println(it.answer)
println(it.difficulty)
}
println()
question3.let {
println(it.questionText)
println(it.answer)
println(it.difficulty)
}
println()
}
- Perbarui kode di
main()
untuk membuat instance classQuiz
yang bernamaquiz
.
fun main() {
val quiz = Quiz()
}
- Panggil
printQuiz()
.
fun main() {
val quiz = Quiz()
quiz.printQuiz()
}
- Jalankan kode untuk memastikan semua berfungsi.
Quoth the raven ___ nevermore MEDIUM The sky is green. True or false false EASY How many days are there between full moons? 28 HARD
Memanggil metode objek tanpa variabel menggunakan apply()
Salah satu fitur keren dari fungsi cakupan adalah Anda dapat memanggilnya pada objek bahkan sebelum objek tersebut ditetapkan ke variabel. Misalnya, fungsi apply()
adalah fungsi ekstensi yang dapat dipanggil pada objek menggunakan notasi titik. Fungsi apply()
juga menampilkan referensi ke objek tersebut sehingga dapat disimpan dalam variabel.
Update kode di main()
untuk memanggil fungsi apply()
.
- Panggil
apply()
setelah kurung tutup saat membuat instance classQuiz
. Anda dapat menghilangkan tanda kurung saat memanggilapply()
, dan menggunakan sintaksis lambda di akhir.
val quiz = Quiz().apply {
}
- Pindahkan panggilan ke
printQuiz()
di dalam ekspresi lambda. Anda tidak perlu lagi mereferensikan variabelquiz
atau menggunakan notasi titik.
val quiz = Quiz().apply {
printQuiz()
}
- Fungsi
apply()
menampilkan instance classQuiz
, tetapi karena Anda tidak lagi menggunakannya di mana pun, hapus variabelquiz
. Dengan fungsiapply()
, Anda bahkan tidak memerlukan variabel untuk memanggil metode pada instanceQuiz
.
Quiz().apply {
printQuiz()
}
- Jalankan kode. Perhatikan bahwa Anda dapat memanggil metode ini tanpa referensi ke instance
Quiz
. Fungsiapply()
menampilkan objek yang disimpan diquiz
.
Quoth the raven ___ nevermore MEDIUM The sky is green. True or false false EASY How many days are there between full moons? 28 HARD
Meskipun penggunaan fungsi cakupan tidak wajib untuk mencapai output yang diinginkan, contoh di atas menggambarkan bagaimana fungsi tersebut dapat membuat kode Anda lebih ringkas dan menghindari pengulangan nama variabel yang sama.
Kode di atas hanya menunjukkan dua contoh, tetapi Anda disarankan untuk mem-bookmark dan membaca dokumentasi Fungsi Cakupan ketika menemukan penggunaannya nanti dalam kursus.
9. Ringkasan
Anda baru saja melihat cara kerja beberapa fitur baru Kotlin. Generik memungkinkan jenis data untuk diteruskan sebagai parameter ke class, class enum menentukan serangkaian kemungkinan nilai yang terbatas, dan class data membantu menghasilkan beberapa metode yang berguna untuk class secara otomatis.
Anda juga telah melihat cara membuat objek singleton—yang dibatasi untuk satu instance, cara menjadikannya sebagai objek pendamping class lain, dan cara memperluas class yang ada dengan properti get-only yang baru dan metode baru. Terakhir, Anda telah melihat beberapa contoh bagaimana fungsi cakupan dapat memberikan sintaksis yang lebih sederhana saat mengakses properti dan metode.
Anda akan melihat konsep ini di seluruh unit berikutnya saat Anda mempelajari lebih lanjut Kotlin, pengembangan Android, dan Compose. Anda kini memiliki pemahaman yang lebih baik tentang cara kerjanya dan bagaimana caranya dapat meningkatkan penggunaan kembali dan keterbacaan kode Anda.