Object-oriented programming (OOP) bukan sekadar cara menulis class, melainkan cara berpikir tentang data dan perilaku sebagai satu kesatuan. Saat program tumbuh menjadi ratusan function yang saling memanggil, gaya prosedural mulai kehilangan struktur dan sulit dipelihara. OOP menawarkan model yang membungkus data bersama perilaku di dalam satu unit bernama class. Pada chapter ini kita akan memetakan perbedaan gaya prosedural dengan gaya OOP, lalu meninjau empat pilar utama yang menjadi fondasi paradigma ini di Kotlin.
Pola Pikir Prosedural vs OOP #
Dalam gaya prosedural, data dan function berdiri sendiri-sendiri. Sebuah struct atau variabel global menyimpan data, sementara function terpisah memanipulasi data tersebut. Pendekatan ini bekerja dengan baik untuk program kecil, namun ketika data dapat diakses dari banyak sisi, perubahan kecil pada struktur data berpotensi memutus banyak function.
OOP membalik pendekatan tersebut. Data dan perilaku yang relevan dikumpulkan di dalam satu class. Object — instans dari class — bertanggung jawab menjaga konsistensi datanya sendiri melalui method yang dipublikasikan. Kode pemanggil tidak menyentuh field secara langsung, melainkan memanggil method yang sudah memvalidasi setiap perubahan.
Perhatikan tabel berikut yang membandingkan kedua gaya pada kasus pengelolaan rekening bank:
| Aspek | Prosedural | OOP |
|---|---|---|
| Lokasi data | Variabel global atau struct terpisah | Field di dalam object |
| Lokasi perilaku | Function global memanipulasi data | Method di dalam class |
| Penegakan aturan | Setiap pemanggil harus ingat memvalidasi | Method class memvalidasi otomatis |
| Risiko perubahan | Struktur data berubah → banyak function rusak | Internal class berubah → API publik tetap |
| Unit pengujian | Function individual | Object dengan state terisolasi |
Empat Pilar OOP #
Kotlin mewarisi empat pilar klasik OOP: encapsulation, inheritance, polymorphism, dan abstraction. Keempatnya saling melengkapi dan muncul bersamaan pada kode nyata.
Encapsulation #
Encapsulation adalah pembungkusan data bersama perilaku yang mengelolanya. Data internal disembunyikan dengan modifier private, sementara akses dari luar hanya melalui method atau property yang sengaja dipublikasikan. Dengan begitu, class tetap dapat menjaga invarian — misalnya saldo rekening tidak boleh negatif — tanpa mempercayai pemanggil untuk memeriksa sendiri.
Inheritance #
Inheritance memungkinkan sebuah class mewarisi struktur dan perilaku dari class lain. Class anak (subclass) otomatis memiliki field dan method milik class induk (superclass), dan dapat menambah perilaku baru. Kotlin menerapkan inheritance secara eksplisit: class induk harus ditandai open, dan subclass menandai pewarisan dengan tanda titik dua serta memanggil constructor induk.
Polymorphism #
Polymorphism membuat satu referensi dapat menunjuk ke banyak bentuk object konkret. Variabel bertipe Hewan dapat menampung instance Kucing maupun Anjing, dan pemanggilan method bersuara() akan memilih implementasi yang sesuai dengan jenis object saat runtime. Mekanisme ini disebut dynamic dispatch.
Abstraction #
Abstraction berfokus pada antarmuka yang terlihat, bukan detail implementasi. Class abstrak atau interface mendefinisikan kontrak (method apa yang harus ada) tanpa memaksa implementasi tertentu. Pemanggil cukup tahu bahwa setiap Hewan bisa bersuara(), tanpa perlu tahu mekanisme suara di dalam subclass.
Contoh Empat Pilar dalam Satu Program #
Contoh berikut menggabungkan keempat pilar dalam satu program ringkas. Class Hewan bersifat abstrak dan menyembunyikan detail implementasi bersuara(). Subclass Kucing dan Anjing mewarisi field nama dan mengimplementasikan method abstrak tersebut. Class RekeningSaldo memperlihatkan encapsulation dengan menyembunyikan field saldo di balik method publik yang memvalidasi setiap perubahan.
// konsep_oop.kt
// Mendemonstrasikan empat pilar OOP: encapsulation, inheritance, polymorphism, abstraction.
// Abstraction: class abstrak menyembunyikan detail implementasi.
abstract class Hewan(val nama: String) {
abstract fun bersuara(): String
}
// Inheritance + Polymorphism: subclass menambah perilaku sendiri.
class Kucing(nama: String) : Hewan(nama) {
override fun bersuara(): String = "Meong"
}
class Anjing(nama: String) : Hewan(nama) {
override fun bersuara(): String = "Guk guk"
}
// Encapsulation: data tersembunyi di balik property private + akses terkontrol.
class RekeningSaldo(pemilik: String, saldoAwal: Int) {
private var saldo: Int = saldoAwal
val pemilik: String = pemilik
fun setor(jumlah: Int) {
if (jumlah > 0) {
saldo += jumlah
}
}
fun tarik(jumlah: Int): Boolean {
if (jumlah <= 0 || jumlah > saldo) {
return false
}
saldo -= jumlah
return true
}
fun infoSaldo(): Int = saldo
}
fun main() {
val daftarHewan: List<Hewan> = listOf(
Kucing("Miko"),
Anjing("Rex")
)
for (hewan in daftarHewan) {
println("${hewan.nama} bersuara: ${hewan.bersuara()}")
}
val rekening = RekeningSaldo("Budi", 100000)
rekening.setor(50000)
val berhasilTarik = rekening.tarik(30000)
println("Tarik berhasil: $berhasilTarik")
println("Saldo akhir ${rekening.pemilik}: ${rekening.infoSaldo()}")
}Output yang Dihasilkan:
Miko bersuara: Meong
Rex bersuara: Guk guk
Tarik berhasil: true
Saldo akhir Budi: 120000Mari kita amati peran setiap bagian:
abstract class Hewan(val nama: String): class abstrak menyediakan fieldnamatetapi tidak menyediakan implementasibersuara(). Subclass wajib mengisi implementasi tersebut — ini adalah abstraction.class Kucing(nama: String) : Hewan(nama): tanda titik dua menandakan inheritance. ConstructorKucingmendelegasikannamake constructorHewan.override fun bersuara(): String: keywordoverridemengganti implementasi method abstrak. Saat diakses melalui variabel bertipeHewan, runtime memilih implementasiKucingatauAnjingsesuai tipe asli object — ini adalah polymorphism.private var saldo: fieldsaldotidak dapat diakses dari luar class. Perubahan hanya melalui methodsetordantarikyang memvalidasi nilai masukan — ini adalah encapsulation.tarik(jumlah: Int): Boolean: method mengembalikanBooleanuntuk menandakan keberhasilan. Pemanggil tidak pernah menyentuhsaldosecara langsung, sehingga invarian “saldo tidak negatif” selalu terjaga.
Mengapa Pilar Penting bagi Kode Berskala #
Referensi Lanjutan #
- Classes and Inheritance — tinjauan resmi deklarasi class dan inheritance di Kotlin
- Inheritance — konsep dasar pewarisan sebagai salah satu pilar OOP
Empat pilar bukan kewajiban sintaksis, melainkan panduan desain. Encapsulation menjaga konsistensi data saat banyak developer bekerja pada basis kode yang sama. Inheritance mengurangi duplikasi perilaku yang serupa antar class. Polymorphism membuat kode pemanggil tidak perlu tahu subclass konkret yang sedang dipakai. Abstraction memisahkan kontrak dari implementasi. Pada chapter Class dan Object berikutnya, kita akan mulai menulis class konkret pertama dan melihat bagaimana pendeklarasian class memengaruhi cara object dibuat dan digunakan.