26. Method dan self — Solusi PR

File solusi .lua ada di exercises/26/homework/solutions/.

Soal 1 — Objek counter

Soal. Sebuah tabel counter dengan method inc, get, dan reset.

Cara berpikir. Tabel menyimpan state di count; setiap method membaca atau mengubah self.count. Definisi dan pemanggilan dengan titik dua mengurus koneksinya.

Solusi lengkap.

local counter = { count = 0 }

function counter:inc()
    self.count = self.count + 1
end

function counter:get()
    return self.count
end

function counter:reset()
    self.count = 0
end

counter:inc()
counter:inc()
counter:inc()
print(counter:get())   -- 3

counter:reset()
print(counter:get())   -- 0

Kesalahan umum.

  • Menulis function counter.inc(self) — bentuk yang diperluas ini memang bisa jalan, tapi terkesan seperti lupa memakai titik dua. Pilih satu gaya saja dan konsisten.
  • Memanggil counter.inc() bukan counter:inc(). Tanpa titik dua, self bernilai nil dan assignment-nya gagal.

Soal 2 — Anjing dengan bark

Soal. Dua tabel anjing dengan nama masing-masing, masing-masing memanggil :bark()-nya sendiri.

Cara berpikir. Dua tabel yang masing-masing punya field name bisa berbagi method yang sama, atau masing-masing mendefinisikan bark-nya sendiri. Keduanya bisa jalan; cara kedua boros memori dan jarang sepadan.

Solusi lengkap.

local function makeDog(name)
    local dog = { name = name }
    function dog:bark()
        print(self.name .. ": Woof!")
    end
    return dog
end

local rex = makeDog("Rex")
local lassie = makeDog("Lassie")

rex:bark()      -- Rex: Woof!
lassie:bark()   -- Lassie: Woof!

Versi yang lebih sederhana tanpa fungsi pembantu:

local rex = { name = "Rex" }
function rex:bark()
    print(self.name .. ": Woof!")
end

local lassie = { name = "Lassie" }
function lassie:bark()
    print(self.name .. ": Woof!")
end

rex:bark()
lassie:bark()

Keduanya menghasilkan output yang sama. Bab 27 mengembangkan cara pertama menjadi class yang sesungguhnya.

Kesalahan umum.

  • Berbagi satu method bark dengan menulis lassie.bark = rex.bark. Cara itu sebenarnya bisa jalan — hasilnya tetap mencetak Lassie: Woof! karena self.name dibaca dari tabel mana pun yang memulai pemanggilan — dan ini merupakan optimasi yang valid. Asalkan kamu tahu apa yang sedang dilakukan.

Soal 3 — Objek kalkulator

Soal. Sebuah tabel calc dengan method aritmetika yang memperbarui nilai internal.

Solusi lengkap.

local calc = { value = 0 }

function calc:add(n)
    self.value = self.value + n
end

function calc:sub(n)
    self.value = self.value - n
end

function calc:mul(n)
    self.value = self.value * n
end

function calc:show()
    print("Value: " .. self.value)
end

calc:add(10)
calc:show()    -- Value: 10
calc:mul(3)
calc:show()    -- Value: 30
calc:sub(5)
calc:show()    -- Value: 25

Kesalahan umum.

  • Mengembalikan nilai baru dari setiap method alih-alih memperbarui self.value. Keduanya boleh, tapi soal meminta pembaruan di tempat (in-place), jadi method-nya memodifikasi self.value.

Tantangan — Stack

Soal. Sebuah stack LIFO dengan method push, pop, peek, size.

Solusi lengkap.

local stack = { items = {} }

function stack:push(v)
    table.insert(self.items, v)
end

function stack:pop()
    return table.remove(self.items)
end

function stack:peek()
    return self.items[#self.items]
end

function stack:size()
    return #self.items
end

stack:push("a")
stack:push("b")
stack:push("c")
print(stack:size())    -- 3
print(stack:peek())    -- c
print(stack:pop())     -- c
print(stack:pop())     -- b
print(stack:size())    -- 1

Tanpa argumen posisi, table.insert dan table.remove bekerja di ujung list — posisi yang tepat untuk operasi stack.

Kesalahan umum.

  • Menggunakan table.insert(self.items, 1, v) dan table.remove(self.items, 1). Cara itu menyisipkan di depan — itu queue, bukan stack — dan lebih lambat karena setiap item lain harus bergeser.
  • Lupa mengembalikan nilai dari pop dan peek. Nilai kembalian default-nya adalah nil, yang akan membuat pengujian gagal tanpa pesan error.

Selesai?

Bab berikutnya mengembangkan pola ini lebih jauh. Dengan metatable, kamu tidak perlu lagi menulis boilerplate yang sama untuk setiap "class" dan bisa berbagi tabel method di banyak instance.