[Lua] 如何模擬單繼承OO、實現抽象工廠

来源:https://www.cnblogs.com/linxiaoxu/archive/2023/08/25/17657818.html
-Advertisement-
Play Games

## 所有類的基類 Object Lua 沒有嚴格的 oo(Object-Oriented)定義,可以利用元表特性來實現 先定義所有類的基類,即`Object`類。代碼順序從上到下,自成一體。[完整代碼](#oo.lua) 定義一個空表 `Object` ,`__index` 指向其自身(繼承將直接 ...


所有類的基類 Object

Lua 沒有嚴格的 oo(Object-Oriented)定義,可以利用元表特性來實現

先定義所有類的基類,即Object類。代碼順序從上到下,自成一體。完整代碼

定義一個空表 Object__index 指向其自身(繼承將直接使用該表作為對象的元表)

Object = {}
Object.__index = Object

new 定義構造對象時的初始化行為,相當於構造器。基類不需要進行任何初始化操作

function Object:new()
end

extend 實現了類繼承,具體流程

  • 創建一個空表 cls,作為類
  • 我們將父類的元方法全部複製給子類 ⭐為什麼
  • 子類的 __index 指向其自身(子類可被繼承)(覆蓋了父類複製給子類的 __index
  • 子類的 super 欄位指向父類
  • 子類的元表指向父類(子類)
function Object:extend()
  local cls = {}
  for k, v in pairs(self) do
    if k:find("__") == 1 then
      cls[k] = v
    end
  end
  cls.__index = cls
  cls.super = self
  setmetatable(cls, self)
  return cls
end

implement 用於實現介面類,可傳入多個介面

  • 遍歷每個介面 cls
  • 當前對象如果沒有實現介面類的某個方法,則將該方法的實現從介面類複製給對象
function Object:implement(...)
  for _, cls in pairs({ ... }) do
    for k, v in pairs(cls) do
      if self[k] == nil and type(v) == "function" then
        self[k] = v
      end
    end
  end
end

is用於判斷某個類或對象實例是否是另一個類

  • 迴圈拿元表,直到沒有為止,最後一個元表一定是 Object
function Object:is(T)
  local mt = getmetatable(self)
  while mt do
    if mt == T then
      return true
    end
    mt = getmetatable(mt)
  end
  return false
end

__tostring 用於對象 printtostring 時自定義字元串化

function Object:__tostring()
  return "Object"
end

直接用類名稱,來實現一個對象的實例化。__call 可以把變數當函數使用,比如Car類(變數),local mycar = Car(),生成了一個對象實例myCar,屬於類Car

  • 創建一個對象(空表),並把自身(類)作為對象的元表
  • 執行構造器,由於對象是空表找不到,所以通過元表的__index也就是去父類找
  • 返回初始化好的對象實例
function Object:__call(...)
  local obj = setmetatable({}, self)
  obj:new(...)
  return obj
end

全局函數 unrealized用於模擬介面或抽象類未定義的方法,子類未實現時會寄

function unrealized(...)
  error("未實現", 2)
end

到現在為止已經模擬了一個單繼承OO,在需要的地方導入模塊,使用 Objectunrealized 這兩個全局變數


實驗-抽象工廠

接下來實現抽象工廠模式。抽象工廠能創建一系列相關的對象,而無需指定其具體類。

考慮如下情況,有多類敵人(正方形、圓形、長條),敵人初始化是兩種狀態的一種(正常狀態,厚血狀態),且後期敵人和狀態種類還會增多

我們先定義敵人抽象類

Enemy = Object:extend()

Enemy.draw = unrealized
Enemy.new = function(self)
  self.hp = 100
end

然後定義繼承抽象類Enemy的抽象類SquareEnemy,與繼承抽象類SquareEnemy的兩個普通類SquareEnemyWhiteSquareEnemyRed。圓形敵人跟長條敵人同理。

SquareEnemy = Enemy:extend()
SquareEnemy.new = function(self, x, y, w)
  SquareEnemy.super.new(self)
  self.x = x
  self.y = y
  self.w = w
end

SquareEnemyWhite = SquareEnemy:extend()
SquareEnemyWhite.draw = function(self)
  love.graphics.setColor(1, 1, 1)
  love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
end

SquareEnemyRed = SquareEnemy:extend()
SquareEnemyRed.new = function(self, ...)
  SquareEnemyRed.super.new(self, ...)
  self.hp = 200
end
SquareEnemyRed.draw = function(self)
  love.graphics.setColor(1, 0, 0)
  love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
end

定義工廠介面,在這裡介面算是一種特殊的抽象類(由於只能用表來模擬介面,所以讓介面也繼承Objcet)

IFactory = Object:extend()

IFactory.circleEnemy = unrealized
IFactory.squareEnemy = unrealized
IFactory.barEnemy = unrealized

分別實現白色工廠和紅色工廠(如果沒有額外的創建操作,可以不用return)

WhiteFactory = Object:extend()
WhiteFactory:implement(IFactory)

WhiteFactory.circleEnemy = function(...)
  return CircleEnemyWhite(...)
end

WhiteFactory.squareEnemy = function(...)
  return SquareEnemyWhite(...)
end

WhiteFactory.barEnemy = function(...)
  return BarEnemyWhite(...)
end

RedFactory = Object:extend()
RedFactory:implement(IFactory)

RedFactory.circleEnemy = function(...)
  return CircleEnemyRed(...)
end

RedFactory.squareEnemy = function(...)
  return SquareEnemyRed(...)
end

RedFactory.barEnemy = function(...)
  return BarEnemyRed(...)
end

接下來測試抽象工廠

require 'oo'
require 'enemy.aac'
require 'enemy.bar'
require 'enemy.circle'
require 'enemy.square'
require 'factory.aac'
require 'factory.red_factory'
require 'factory.white_factory'

enemies = {}
love.load = function()
  IFactory = WhiteFactory()
  table.insert(enemies, IFactory.circleEnemy(100, 100, 25))
  table.insert(enemies, IFactory.squareEnemy(100, 200, 25))
  table.insert(enemies, IFactory.barEnemy(100, 300, 10, 50))
  IFactory = RedFactory()
  table.insert(enemies, IFactory.circleEnemy(200, 100, 25))
  table.insert(enemies, IFactory.squareEnemy(200, 200, 25))
  table.insert(enemies, IFactory.barEnemy(200, 300, 10, 50))
  for _, enemy in pairs(enemies) do
    print(enemy.hp)
  end
end

love.draw = function()
  for _, enemy in ipairs(enemies) do
    enemy:draw()
  end
end
image-20230825195416157

參考資料

  • 《Lua程式設計·第四版》羅伯托·耶魯薩林斯希 、第227~241頁

其它

oo.lua

Object = {}
Object.__index = Object

function Object:new()
end

function Object:extend()
  local cls = {}
  for k, v in pairs(self) do
    if k:find("__") == 1 then
      cls[k] = v
    end
  end
  cls.__index = cls
  cls.super = self
  setmetatable(cls, self)
  return cls
end

function Object:implement(...)
  for _, cls in pairs({ ... }) do
    for k, v in pairs(cls) do
      if self[k] == nil and type(v) == "function" then
        self[k] = v
      end
    end
  end
end

function Object:is(T)
  local mt = getmetatable(self)
  while mt do
    if mt == T then
      return true
    end
    mt = getmetatable(mt)
  end
  return false
end

function Object:__tostring()
  return "Object"
end

function Object:__call(...)
  local obj = setmetatable({}, self)
  obj:new(...)
  return obj
end

function unrealized(...)
  error("未實現", 3)
end

-- return Object

QUESTION1

如果不複製元方法,假設類B繼承類A,類B的對象實例b,b的元表是類B,在調用 b + b 時,涉及到算術運算符相關的元方法,b會在父類B中查找__add,找不到並不會順著B的元表__index再去B的父類A找,因此會報錯

A = {
  __index = A,
  __add = function(a, b)
    return a.age + b.age
  end,
  name = "小白"
}
B = { __index = B, }
b = { __index = b, age = 1 }

setmetatable(B, A)
setmetatable(b, B)

print(b.name)
print(b + b)
--[[
> dofile 'TEST/test.lua'
小白
TEST/test.lua:15: attempt to perform arithmetic on a table value (global 'b')
stack traceback:
        TEST/test.lua:15: in main chunk
        [C]: in function 'dofile'
        stdin:1: in main chunk
        [C]: in ?
]]

點我返回



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文分享自華為雲社區《華為雲GES:十年磨一劍,打造業界一流的雲原生分散式圖資料庫》,作者:GES圖引擎服務小圖 。 1、淺談雲原生圖資料庫 圖資料庫(graph database)是一個使用圖結構進行語義查詢的資料庫,它使用節點、邊和屬性來表示和存儲數據。該系統的關鍵概念是圖,它直接將存儲中的數據 ...
  • **塑造軟體新生態,賦能發展新變革。** **8月31日-9月2日**,第二十五屆**中國國際軟體博覽會**將於天津梅江會展中心召開。本屆軟博會由中國電子信息行業聯合會主辦,聚焦全球軟體前沿技術與產業發展方向,充分展示軟體賦能數字經濟、信息技術應用創新、工業互聯網平臺、智能製造及元宇宙等多領域發展成 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 最近有一個需求,要我實現一個動畫效果,效果如下 簡單分析了一下效果,是一個3d的效果,首先是一個圓,接著是兩段圓環,第三層是一堆小圓環,最裡面是一些線上運動,有著漸變色的矩形。 第一層的圓環很簡單。 第二層的圓環其實也挺簡單的,只要在設置 ...
  • 當涉及到優化 Flutter 應用時,考慮性能、UI 渲染和記憶體管理是至關重要的。在本篇文章中,我們將通過實例深入討論這些主題,展示如何通過優化技巧改進你的 Flutter 應用。 ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:佳嵐 ### 前言 `Cookie`實際上是一小段的文本信息,它產生的原因是由於HTTP 協議是**無 ...
  • # 模板頁的重要性 Vue 項目中使用佈局組件來創建頁面佈局的方式是完全可行的,而且在很多項目中都被廣泛採用,包括像 ruoyi 這樣的框架。這種模式有助於實現統一的頁面佈局結構,減少重覆代碼,並提高代碼的可維護性。 讓我們具體分析一下你提到的 ruoyi 框架的做法: 1. **Layout 組件 ...
  • 環境: SpringBoot2.7.8 背景: 在增加出庫訂單時需要對物品表的的數量進行修改 因此我在OutboundController中創建了幾個公共方法,並將其註入到Spring中,結果給我報了這一串錯誤。 Description:The dependencies of some of the ...
  • [TOC] 本文主要介紹ImGui應用中的一些界面優化方法,如果是第一次使用ImGui推薦從上一篇文章開始:[使用C++界面框架ImGUI開發一個簡單程式](https://www.cnblogs.com/timefiles/p/17632348.html),最終的界面效果如下: ![image]( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...