27. OOP sederhana dengan metatable — Solusi PR
File solusi .lua ada di
exercises/27/homework/solutions/.
Soal 1 — Class Point dengan move
Soal. Class Point dengan .new,
:distance, dan :move.
Cara memikirkannya. Mulai dari class Point di bab
ini. Tambahkan satu method, :move(dx, dy), yang mengubah
nilai self.x dan self.y.
Solusi lengkap.
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
function Point:move(dx, dy)
self.x = self.x + dx
self.y = self.y + dy
end
local a = Point.new(0, 0)
local b = Point.new(3, 4)
print(a:distance(b)) -- 5.0
a:move(1, 1)
print(a.x, a.y) -- 1 1
print(a:distance(b)) -- sqrt(2^2 + 3^2) ~ 3.6055...Kesalahan umum.
- Lupa menulis
Point.__index = Point. Tanpa baris itu,a:distance(b)tidak bisa menemukan method-nya, dan Lua akan melaporkan error attempt to call a nil value.
Soal 2 — Class Character
Solusi lengkap.
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
function Character:report()
print(string.format("%s: %d / %d HP (alive: %s)",
self.name, self.hp, self.max_hp, tostring(self:isAlive())))
end
local c = Character.new("Keiko", 100)
c:report()
c:takeDamage(30)
c:report()
c:takeDamage(80)
c:report()
c:heal(20)
c:report()Contoh keluaran:
Keiko: 100 / 100 HP (alive: true)
Keiko: 70 / 100 HP (alive: true)
Keiko: 0 / 100 HP (alive: false)
Keiko: 20 / 100 HP (alive: true)
Character:report adalah tempat yang pas untuk
string.format — tiga nilai dalam satu baris template yang
tetap.
Kesalahan umum.
- Membiarkan
hpturun di bawah nol atau naik melebihimax_hp. Dua pengecekanifditakeDamagedanhealmembatasi nilainya — mirip fungsiclampdari PR bab 21, diterapkan sebagai method.
Soal 3 — Class Rectangle
Solusi lengkap.
local Rectangle = {}
Rectangle.__index = Rectangle
function Rectangle.new(w, h)
local self = setmetatable({}, Rectangle)
self.w = w
self.h = h
return self
end
function Rectangle:area()
return self.w * self.h
end
function Rectangle:perimeter()
return 2 * (self.w + self.h)
end
local r1 = Rectangle.new(3, 4)
local r2 = Rectangle.new(10, 2)
print(r1:area()) -- 12
print(r1:perimeter()) -- 14
print(r2:area()) -- 20
print(r2:perimeter()) -- 24Kesalahan umum.
- Menyimpan
widthdanheightsebagai variabel lokal di dalam method, bukan sebagaiself.wdanself.h. Tujuan objek adalah agar field-nya tetap melekat pada instance.
Tantangan — Animal, Dog, Cat
Solusi lengkap.
-- Base class
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 Animal
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
local self = Animal.new(name)
return setmetatable(self, Dog)
end
function Dog:bark()
print(self.name .. ": Woof!")
end
-- Cat inherits Animal
local Cat = setmetatable({}, { __index = Animal })
Cat.__index = Cat
function Cat.new(name)
local self = Animal.new(name)
return setmetatable(self, Cat)
end
function Cat:meow()
print(self.name .. ": Meow.")
end
local rex = Dog.new("Rex")
local whiskers = Cat.new("Whiskers")
rex:describe() -- I am Rex.
whiskers:describe() -- I am Whiskers.
rex:bark() -- Rex: Woof!
whiskers:meow() -- Whiskers: Meow.Dog.new dan Cat.new membangun instance
Animal terlebih dahulu, lalu mengganti
metatable-nya ke class yang lebih spesifik. Rantai pencarian sekarang
menjadi instance -> Dog ->
Animal (atau instance -> Cat
-> Animal).
Kesalahan umum.
- Lupa menulis
Dog.__index = Dog. Kalau tidak ada, method yang didefinisikan diDogtidak bisa dijangkau dari instance-nya; pencarian langsung melompat keAnimal, karena itulah yang ditunjuk oleh metatable milikDog(bukanDogitu sendiri). Ada dua baris__index, pada dua objek berbeda, yang mengerjakan dua hal berbeda.
Selesai?
Class berbasis metatable (Point, Character, Rectangle) dan rantai pewarisan kecil (Animal -> Dog/Cat) kini sudah masuk ke kotak perkakas kamu. Tiga bab berikutnya membangun di atas fondasi ini: Banyak objek sekaligus mengelola kumpulan instance, Pewarisan lebih dalam membuat satu class khusus dari class lain, dan Merancang class kecil membahas cara membuat class yang enak dipakai.