【Lua 筆記】物件導向程式設計(Object Oriented Programming,OOP) - part 11
===
目錄(Table of Contents):
[TOC]
---
由於有款遊戲叫做 CSO(Counter-Strike Online),內建模式創世者模式(Studio)新增使用 Lua 及其遊戲的 API,所以突發奇想製作這個筆記。
這個筆記會在一開始先著重純粹的程式設計自學,在最後的章節才會與 CSO 遊戲 API 進行應用。
小提醒:此為本筆記最後一章,恭喜你可以準備結合CSO的API實作了。
物件導向程式設計(Object Oriented Programming,OOP)
---
物件導向程式設計(Object-Oriented Programming, OOP)是一種程式設計範式,強調使用「物件」來設計軟體和程式碼。物件是具有狀態(屬性)和行為(方法)的實體。OOP 的主要目的是提高程式碼的可重用性、可維護性和可擴展性。
舉個例子:比如我現在正在 coding,用物件導向程式設計的邏輯來看的話,就是如以下表格所示(參考自:[何謂物件導向程式設計 - HackMD](https://hackmd.io/@metal35x/rk2uiTnXI)):
| 類別(class) | 物件名稱(name) | 物件屬性(property) | 物件操作方法(method) |
| -------- | -------- | -------- | -------- |
| 人類 | 我(LukeTseng) | 具有靈活的雙手 | 操作鍵盤滑鼠 |
| 電腦 | 個人電腦 PC | CPU:i5-6400、GPU:MSI RTX 4060 Ti 8G 等 | 控制各個元件 |
| 鍵盤 | Logic Signature K855 | 具有許多鍵位(如:WASD等) | 鍵盤輸入文字 |
| 螢幕 | MSI(我忘記是啥型號了XD) | 100 Hz、內建喇叭等 | 顯示彩色畫面、文字 |
物件,想像成是一個形體或物品,就好比我這個個體就是一種物件,電腦也是一種物件。但是細分下去,CPU、GPU等等也都可以被稱為是一種物件。
回歸到 Lua 本身,其實他並不是完全或原生的物件導向的程式語言,但是我們可以透過 table、metatable 模擬出物件導向的特性。
以下是關於物件導向的特性:
1. **封裝(Encapsulation)**:將物件的狀態(屬性)和行為(方法)封裝在一起,並隱藏物件的內部實際細節。這樣可以保護物件的狀態不被外部直接修改,只能通過物件的方法來運算。
2. **繼承(Inheritance)**:能讓一個類別(子類別)從另一個類別(父類別)繼承屬性和方法。這樣可以重複使用父類別的程式碼,並在子類別中擴展或修改其行為(方法)。
3. **多型(Polymorphism)**:能讓不同的物件以相同的方式響應相同的方法呼叫。表示可以用相同的介面來操作不同型態的物件,且不需要知道它們的具體型態。(簡短說就是指同一行為能有不同結果,例如滑鼠左鍵點某個網站連結直接導向那個網站,在網站裡面點各個按鈕又是不一樣的結果)
4. **抽象(Abstraction)**:指只顯示物件的必要部分,隱藏不必要的細節。可以簡化複雜系統的設計,讓 user 只需要用到重要的部分。
關於上述四點,筆者這邊用 python 的例子來舉例:
```python=
# 封裝(Encapsulation)
class Person:
def __init__(self, name, age):
self.__name = name # 私有屬性 -> 封裝特性:只有這個類別裡面的方法才能用這個物件
self.__age = age # 私有屬性 -> 封裝特性:只有這個類別裡面的方法才能用這個物件
def get_name(self):
return self.__name
def set_age(self, age):
if age > 0:
self.__age = age
```
```python=
# 繼承(Inheritance)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal): # Dog 類別繼承 Animal 類別,所以 Dog 可以用 Animal 的方法跟物件
def speak(self):
return "Woof!"
```
```python=
# 多型(Polymorphism)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
def make_animal_speak(animal):
print(animal.speak()) # 同種呼叫方式有不同結果(因為不同動物:dog、cat)
dog = Dog("Buddy")
cat = Cat("Whiskers")
make_animal_speak(dog) # Woof!
make_animal_speak(cat) # Meow!
```
抽象的部分就不舉例了(因為抽象概念其實好了解,另外程式上也用到語法糖的語法,只顯示重要的部份給 user,簡化使用者介面的複雜程度)
接下來讓我們進入 Lua 版本的物件導向程式設計
### Lua 物件導向
---
物件是由屬性和方法所組成的。Lua 中最基本的資料結構是 table,所以需要用 table 來描述物件的屬性。
方法其實就是由函數(function)所組成的,所以可以在 lua 當中用 table + function 重現 class。
以下是用表與函數結合的模擬類別(來自:[Lua 面向对象 | 菜鸟教程](https://www.runoob.com/lua/lua-object-oriented.html)):
```lua=
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
```
這個函數的作用域被保存在 Account 裡面做使用,也就是所謂的方法(Method),可以用以下這方式呼叫這個函數:
```
Account.withdraw(100.00)
```
是不是就有點像之前學過的字串方法呢?像是:string.upper() 一樣。
以下是筆者自創範例:
```lua=
-- 定義 Person 類別
Person = {}
-- Person 類別的建構方法
function Person:new(name, age)
local newObj = { name = name, age = age }
self.__index = self
return setmetatable(newObj, self)
end
-- Person 類別的方法:顯示個人資訊
function Person:showInfo()
print("Name: " .. self.name .. ", Age: " .. self.age)
end
-- 創建 Person 類別的實例
local person1 = Person:new("John", 30)
-- 呼叫實例的方法
person1:showInfo()
-- 存取 person1 的屬性
print(person1.age)
```
:::info
建構方法(又稱構造函數:Constructor):主要用於在創建物件時「初始化」物件,給物件成員變數(屬性)賦予初始值。
`local person1 = Person:new("John", 30)` 創建一個物件(實例:Instance,一個具體的物件,有詳細的內容,在這範例當中包含人名跟年齡)。實例就直接看成一個個體,像筆者 LukeTseng 是一個個體,更詳細一點內容就是我的身高年齡血壓等等,每個實例的屬性值可以是不同的,表示每個實例都是獨立的,擁有自己的狀態。
`print(person1.age)` 存取 person1 實例的屬性。
:::
:::info
**為什麼用冒號 `:`?**
```lua
-- 使用點(.)呼叫
person1.showInfo(Person)
-- 使用冒號(:)呼叫
person1:showInfo()
```
用 `.` 需要明確輸入參數進去,用冒號的話,Lua 自動將 Person 作為 self 參數傳遞給showInfo 函數,使呼叫動作更加簡潔、直觀。
:::
### Lua 繼承
---
可以透過 metatable 實現。
```lua=
-- 父類別 Animal 定義
Animal = {}
Animal.__index = Animal
function Animal:new(name)
local instance = setmetatable({}, Animal)
instance.name = name
return instance
end
function Animal:speak()
print(self.name .. " makes a sound.")
end
-- 子類別 Dog 繼承自 Animal
Dog = setmetatable({}, {__index = Animal})
function Dog:new(name)
local instance = setmetatable(Animal:new(name), Dog)
return instance
end
function Dog:speak()
print(self.name .. " barks.")
end
local animal = Animal:new("Some animal")
animal:speak() -- Some animal makes a sound.
local dog = Dog:new("Rex")
dog:speak() -- Rex barks.
```
總結
---
物件導向程式設計(Object-Oriented Programming, OOP)是一種強調使用「物件」來設計軟體和程式碼的程式設計範式。物件是具有狀態(屬性)和行為(方法)的實體。OOP 的主要目的是提高程式碼的可重用性、可維護性和可擴展性。
OOP 的四大特性包括:
* 封裝(Encapsulation):將物件的狀態和行為封裝在一起,隱藏內部細節,只能通過物件的方法來操作狀態。
* 繼承(Inheritance):允許一個類別(子類別)從另一個類別(父類別)繼承屬性和方法,實現程式碼的重用。
* 多型(Polymorphism):不同的物件可以以相同的方式響應相同的方法呼叫,實現同一行為有不同的執行結果。(簡單來說就是同一行為有不同結果)
* 抽象(Abstraction):隱藏不必要的細節,只顯示物件的必要部分,簡化系統設計。
Lua 雖然不是原生物件導向的語言,但可以透過 table 和 metatable 來模擬類別(class)、繼承(inheritance)等物件導向的特性。
Lua 用 table 來描述物件的屬性,並通過函數(function)來實現方法。透過 metatable 的機制,Lua 能夠模擬出類別的繼承。
參考資料
---
[何謂物件導向程式設計 - HackMD](https://hackmd.io/@metal35x/rk2uiTnXI)
[Programming in Lua : 16](https://www.lua.org/pil/16.html)
[[Lua] 程式設計教學:撰寫基於物件的 (object-based) 程式 | 開源技術教學](https://opensourcedoc.com/lua-programming/object/)
[Lua 面向对象 | 菜鸟教程](https://www.runoob.com/lua/lua-object-oriented.html)