27. OOP Sederhana dengan Metatable
Bab 26 menaruh beberapa method langsung ke sebuah tabel. Cara itu oke
untuk satu objek, tapi buang-buang tenaga kalau kamu butuh
banyak objek dengan bentuk yang sama. Bayangkan 100
anjing — menyalin bark ke masing-masing satu per satu itu
konyol. Bab ini memperkenalkan metatable, lalu
menggunakannya untuk membuat satu tabel method yang dipakai bersama oleh
semua instance.
Di sinilah kode bergaya Roblox mulai terasa familiar.
Part, Player, dan Tool di Roblox
semuanya mengikuti pola ini.
Class dengan .new dan
:method
Berikut ini adalah pola "class" standar di Lua. Baca dua kali, lalu cek penjelasannya:
local Point = {}
Point.__index = Point
function Point.new(x, y)
local self = setmetatable({}, Point)
self.x = x
self.y = y
return self
end
function Point:distance(other)
local dx = self.x - other.x
local dy = self.y - other.y
return math.sqrt(dx * dx + dy * dy)
end
local a = Point.new(0, 0)
local b = Point.new(3, 4)
print(a:distance(b)) -- 5.0Penjelasannya:
Point = {}membuat tabel class — semua method disimpan di sini, didefinisikan sekali saja.Point.__index = Pointadalah baris ajaibnya. Baris ini memberitahu Lua: kalau pencarian kunci pada sebuah instance gagal, cari juga di sini. Metatablenya adalahPointitu sendiri, dengan__indexyang menunjuk balik ke dirinya sendiri.Point.new(x, y)adalah fungsi biasa (perhatikan titik, bukan titik dua). Fungsi ini membuat sebuah instance — tabel kosong — mengatur metatablenya kePoint, mengisi field-fieldnya, lalu mengembalikannya.setmetatable({}, Point)memasang metatable, sehingga tabel kosong itu sekarang tahu: untuk kunci yang tidak ada, cari di dalamPoint.function Point:distance(other)mendefinisikan method dengan titik dua, menjadikanselfsebagai parameter pertama — sama seperti bab 26.a:distance(b)adalah tempat pemanggilan.aadalah sebuah instance, jadi Lua mengecekauntukdistance. Tidak ketemu, maka__indexmengarahkan pencarian kePoint, yang memilikinya. Method berjalan denganself = adanother = b.
Hasilnya: satu tabel method, banyak instance kecil, tidak ada penyalinan.
Buka exercises/27/01-point.lua. Tambahkan method
Point:move(dx, dy) yang menambahkan dx ke
self.x dan dy ke self.y. Buat
sebuah titik, pindahkan dua kali, lalu cetak posisinya.
Kenapa perlu indirection metatable?
Sebuah class butuh metatable, bukan tabel biasa, karena penugasan ke sebuah instance tidak boleh memengaruhi class-nya.
Kalau kamu melewati metatable dan menulis
local self = Point, maka self.x = 0 akan
menulis x = 0 langsung ke class Point itu
sendiri. Setiap "instance" akan berbagi x yang sama — bukan
itu artinya "instance".
setmetatable({}, Point) memberikan tabel kosong baru
untuk setiap instance. Penulisan masuk ke instance; pembacaan jatuh ke
class hanya kalau instance tidak memiliki kuncinya.
Class kedua: Character
Class karakter kecil yang bisa menerima kerusakan:
local Character = {}
Character.__index = Character
function Character.new(name, hp)
local self = setmetatable({}, Character)
self.name = name
self.hp = hp
self.max_hp = hp
return self
end
function Character:takeDamage(amount)
self.hp = self.hp - amount
if self.hp < 0 then
self.hp = 0
end
end
function Character:heal(amount)
self.hp = self.hp + amount
if self.hp > self.max_hp then
self.hp = self.max_hp
end
end
function Character:isAlive()
return self.hp > 0
end
local c = Character.new("Keiko", 100)
c:takeDamage(30)
print(c.hp) -- 70
c:heal(50)
print(c.hp) -- 100 (capped at max_hp)
print(c:isAlive()) -- trueSetiap Character.new(...) menghasilkan instance baru
dengan name, hp, dan max_hp
miliknya sendiri. Keempat method tinggal di Character;
setiap instance menjangkaunya melalui __index.
Pewarisan dengan dua
lompatan __index
Class anak mewarisi dari class induk dengan memberikan metatable
milik class anak rantai __index-nya sendiri. Dua tabel
singkat berikut ini:
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:describe()
print("I am " .. self.name .. ".")
end
-- Dog inherits from Animal
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
local self = Animal.new(name) -- build with Animal's fields
return setmetatable(self, Dog) -- but with Dog as metatable
end
function Dog:bark()
print(self.name .. ": Woof!")
end
local rex = Dog.new("Rex")
rex:describe() -- I am Rex. (inherited from Animal)
rex:bark() -- Rex: Woof! (defined on Dog)Baris kuncinya adalah
local Dog = setmetatable({}, { __index = Animal }). Baris
ini menyatakan: ketika pencarian jatuh ke Dog dan tidak
ketemu, jatuh lagi ke Animal. Jadi
rex:describe() mengecek rex, tidak ketemu;
mengecek Dog, tidak ketemu; mengecek Animal,
ketemu.
Itu sudah cukup untuk sebagian besar keperluan pewarisan. Metatable bisa melakukan lebih banyak (operator overloading, perbandingan kustom, tabel yang bisa dipanggil seperti fungsi), tapi pola-pola ini mencakup semua yang dibutuhkan mini-proyek Bagian 6 dan jembatan Roblox.
PR
Soal 1 — Class Point dengan move
Buka exercises/27/homework/01-point.lua. Buat class
Point seperti di atas, plus method
Point:move(dx, dy) yang menambahkan delta ke posisi. Uji
dengan membuat dua titik, memindahkan salah satu, lalu mencetak jarak di
antara keduanya.
Soal 2 — Class Character
Buka exercises/27/homework/02-character.lua. Buat class
Character dengan .new(name, hp),
:takeDamage(amount), :heal(amount), dan
:isAlive(). Tambahkan method :report() yang
mencetak nama dan HP saat ini. Jalankan pertarungan kecil: serang dua
kali, sembuhkan sekali, dan cetak laporan setelah setiap aksi.
Soal 3 — Class Rectangle
Buka exercises/27/homework/03-rectangle.lua. Buat class
Rectangle dengan .new(width, height),
:area(), dan :perimeter(). Uji dengan dua
persegi panjang berukuran berbeda.
Tantangan — Animal dan Dog
Buka exercises/27/homework/04-inheritance.lua. Buat
Animal dan Dog persis seperti contoh pewarisan
di bab ini. Kemudian tambahkan class anak kedua yaitu
Cat yang juga mewarisi dari Animal dan punya
method :meow() sendiri. Buat satu anjing dan satu kucing,
panggil :describe() pada keduanya, lalu
:bark() pada anjing dan :meow() pada
kucing.
Buntu atau sudah selesai? Buka halaman solusi PR.