30. Merancang class yang baik

Kamu sudah bisa membuat class, membuat instance, dan menggunakan pewarisan. Bab terakhir dari Bagian 6 ini bukan tentang sintaks baru — ini tentang selera desain: merancang class yang nyaman dan aman digunakan. Ide besarnya, enkapsulasi, lebih sederhana dari kedengarannya.

Enkapsulasi: biarkan method menjaga data

Enkapsulasi artinya menjaga data objek tetap valid dengan mengubahnya hanya melalui method-method miliknya sendiri. Kode dari luar cukup meminta objek untuk melakukan sesuatu; ia tidak perlu langsung mengacak-acak field-nya.

Berikut contoh class BankAccount. Saldo tidak boleh pernah negatif, dan kamu tidak bisa menyetor jumlah negatif. Method-lah yang menegakkan aturan-aturan tersebut:

local Account = {}
Account.__index = Account

function Account.new(owner)
    return setmetatable({ owner = owner, balance = 0 }, Account)
end

function Account:deposit(amount)
    if amount > 0 then
        self.balance = self.balance + amount
    end
end

function Account:withdraw(amount)
    if amount > 0 and amount <= self.balance then
        self.balance = self.balance - amount
        return true        -- success
    end
    return false           -- not enough money, or a silly amount
end

function Account:getBalance()
    return self.balance
end

Cara menggunakannya:

local acc = Account.new("Keiko")
acc:deposit(100)
acc:withdraw(30)
print(acc:getBalance())     -- 70

print(acc:withdraw(1000))   -- false  (ditolak: saldo tidak cukup)
print(acc:getBalance())     -- 70     (tidak berubah)

Akun ini tidak bisa mencapai kondisi yang tidak masuk akal: setiap perubahan melewati method yang memeriksa dulu. Kalau kode dari luar bisa menulis acc.balance = -500 langsung, jaminan itu hilang. Jadi kesepakatannya: bicara dengan objek melalui method-nya.

Antarmuka publik yang bersih

Bayangkan sebuah class punya dua sisi:

  • Sisi dalam — field dan logika bantu internalnya. Detail yang mungkin berubah.
  • Sisi luar — method yang dipanggil oleh kode lain. Sekumpulan aksi yang kecil dan jelas: deposit, withdraw, getBalance.

Class yang baik menjaga sisi luar tetap kecil dan jelas. Pengguna class Account-mu hanya butuh tiga kata kerja; mereka tidak perlu tahu bahwa saldo disimpan dalam field bernama balance. Simpan dalam satuan sen besok, dan selama ketiga method berperilaku sama, tidak ada yang rusak.

Beri nama method sebagai aksi

Method melakukan sesuatu, jadi beri nama dengan kata kerja: deposit, withdraw, move, attack, reset. Field menyimpan sesuatu, jadi beri nama dengan kata benda: balance, name, hp. acc:withdraw(30) harus terbaca seperti instruksi — karena memang begitu.

Buka exercises/30/01-account.lua. Di sana ada class BankAccount. Tambahkan method :canAfford(amount) yang mengembalikan true jika saldo minimal sebesar amount, tanpa mengubahnya. Gunakan method itu sebelum melakukan penarikan.

Tiga pertanyaan untuk setiap class

Saat merancang sebuah class, tanyakan:

  1. Apa yang ia tahu? → field-fieldnya (sebuah akun tahu saldo miliknya).
  2. Apa yang bisa ia lakukan? → method-methodnya (setor, tarik, cek).
  3. Apa yang harus selalu benar? → aturan yang dijaga oleh method (saldo tidak pernah negatif).

Jawab ketiga pertanyaan itu dan class-nya hampir menulis dirinya sendiri.

PR

File PR: exercises/30/homework/.

Soal 1 — Class Counter

Buka exercises/30/homework/01-counter.lua. Rancang sebuah Counter yang dimulai dari 0 dengan method :increment(), :get(), dan :reset(). Nilai hitungan hanya boleh berubah melalui method-method tersebut. Tunjukkan cara menghitung, membaca, dan mereset.

Soal 2 — Health yang dibatasi

Buka exercises/30/homework/02-health.lua. Rancang class Health dengan nilai max. :heal(n) menaikkan health tapi tidak pernah melebihi max; :damage(n) menurunkannya tapi tidak pernah di bawah 0; :get() membacanya. Aturan yang harus dijaga: health selalu berada antara 0 dan max.

Soal 3 — Saklar lampu

Buka exercises/30/homework/03-switch.lua. Rancang sebuah Switch yang bisa menyala atau mati. :toggle() membalik kondisinya, :isOn() melaporkan kondisinya. Mulai dalam kondisi mati, toggle beberapa kali, dan cetak kondisinya setelah setiap toggle.

Tantangan — Stack dengan penjaga

Buka exercises/30/homework/04-stack.lua. Rancang sebuah Stack dengan method :push(v), :pop(), dan :size(). Aturan yang harus dijaga: :pop() pada stack kosong tidak boleh crash — kembalikan nil sebagai gantinya. Tunjukkan cara kerjanya, termasuk pop pada stack kosong.

Bingung atau sudah selesai? Buka halaman solusi PR.