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:

AspekProseduralOOP
Lokasi dataVariabel global atau struct terpisahField di dalam object
Lokasi perilakuFunction global memanipulasi dataMethod di dalam class
Penegakan aturanSetiap pemanggil harus ingat memvalidasiMethod class memvalidasi otomatis
Risiko perubahanStruktur data berubah → banyak function rusakInternal class berubah → API publik tetap
Unit pengujianFunction individualObject 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.

kotlin
// 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:

text
Miko bersuara: Meong
Rex bersuara: Guk guk
Tarik berhasil: true
Saldo akhir Budi: 120000

Mari kita amati peran setiap bagian:

  1. abstract class Hewan(val nama: String): class abstrak menyediakan field nama tetapi tidak menyediakan implementasi bersuara(). Subclass wajib mengisi implementasi tersebut — ini adalah abstraction.
  2. class Kucing(nama: String) : Hewan(nama): tanda titik dua menandakan inheritance. Constructor Kucing mendelegasikan nama ke constructor Hewan.
  3. override fun bersuara(): String: keyword override mengganti implementasi method abstrak. Saat diakses melalui variabel bertipe Hewan, runtime memilih implementasi Kucing atau Anjing sesuai tipe asli object — ini adalah polymorphism.
  4. private var saldo: field saldo tidak dapat diakses dari luar class. Perubahan hanya melalui method setor dan tarik yang memvalidasi nilai masukan — ini adalah encapsulation.
  5. tarik(jumlah: Int): Boolean: method mengembalikan Boolean untuk menandakan keberhasilan. Pemanggil tidak pernah menyentuh saldo secara langsung, sehingga invarian “saldo tidak negatif” selalu terjaga.

Mengapa Pilar Penting bagi Kode Berskala #

Referensi Lanjutan #

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.