[Lua] 如何模拟单继承OO、实现抽象工厂

打印 上一主题 下一主题

主题 880|帖子 880|积分 2640

所有类的基类 Object

Lua 没有严格的 oo(Object-Oriented)定义,可以利用元表特性来实现
先定义所有类的基类,即Object类。代码顺序从上到下,自成一体。完整代码
定义一个空表 Object ,__index 指向其自身(继承将直接使用该表作为对象的元表)
  1. Object = {}
  2. Object.__index = Object
复制代码
new 定义构造对象时的初始化行为,相当于构造器。基类不需要进行任何初始化操作
  1. function Object:new()
  2. end
复制代码
extend 实现了类继承,具体流程

  • 创建一个空表 cls,作为类
  • 我们将父类的元方法全部复制给子类  ⭐为什么
  • 子类的 __index 指向其自身(子类可被继承)(覆盖了父类复制给子类的 __index)
  • 子类的 super 字段指向父类
  • 子类的元表指向父类(子类)
  1. function Object:extend()
  2.   local cls = {}
  3.   for k, v in pairs(self) do
  4.     if k:find("__") == 1 then
  5.       cls[k] = v
  6.     end
  7.   end
  8.   cls.__index = cls
  9.   cls.super = self
  10.   setmetatable(cls, self)
  11.   return cls
  12. end
复制代码
implement 用于实现接口类,可传入多个接口

  • 遍历每个接口 cls
  • 当前对象如果没有实现接口类的某个方法,则将该方法的实现从接口类复制给对象
  1. function Object:implement(...)
  2.   for _, cls in pairs({ ... }) do
  3.     for k, v in pairs(cls) do
  4.       if self[k] == nil and type(v) == "function" then
  5.         self[k] = v
  6.       end
  7.     end
  8.   end
  9. end
复制代码
is用于判断某个类或对象实例是否是另一个类

  • 循环拿元表,直到没有为止,最后一个元表一定是 Object
  1. function Object:is(T)
  2.   local mt = getmetatable(self)
  3.   while mt do
  4.     if mt == T then
  5.       return true
  6.     end
  7.     mt = getmetatable(mt)
  8.   end
  9.   return false
  10. end
复制代码
__tostring 用于对象 print 或 tostring 时自定义字符串化
  1. function Object:__tostring()
  2.   return "Object"
  3. end
复制代码
直接用类名称,来实现一个对象的实例化。__call 可以把变量当函数使用,比如Car类(变量),local mycar = Car(),生成了一个对象实例myCar,属于类Car

  • 创建一个对象(空表),并把自身(类)作为对象的元表
  • 执行构造器,由于对象是空表找不到,所以通过元表的__index也就是去父类找
  • 返回初始化好的对象实例
  1. function Object:__call(...)
  2.   local obj = setmetatable({}, self)
  3.   obj:new(...)
  4.   return obj
  5. end
复制代码
全局函数 unrealized用于模拟接口或抽象类未定义的方法,子类未实现时会寄
  1. function unrealized(...)
  2.   error("未实现", 2)
  3. end
复制代码
到现在为止已经模拟了一个单继承OO,在需要的地方导入模块,使用 Object 和 unrealized 这两个全局变量

实验-抽象工厂

接下来实现抽象工厂模式。抽象工厂能创建一系列相关的对象,而无需指定其具体类。
考虑如下情况,有多类敌人(正方形、圆形、长条),敌人初始化是两种状态的一种(正常状态,厚血状态),且后期敌人和状态种类还会增多
我们先定义敌人抽象类
  1. Enemy = Object:extend()
  2. Enemy.draw = unrealized
  3. Enemy.new = function(self)
  4.   self.hp = 100
  5. end
复制代码
然后定义继承抽象类Enemy的抽象类SquareEnemy,与继承抽象类SquareEnemy的两个普通类SquareEnemyWhite、SquareEnemyRed。圆形敌人跟长条敌人同理。
  1. SquareEnemy = Enemy:extend()
  2. SquareEnemy.new = function(self, x, y, w)
  3.   SquareEnemy.super.new(self)
  4.   self.x = x
  5.   self.y = y
  6.   self.w = w
  7. end
  8. SquareEnemyWhite = SquareEnemy:extend()
  9. SquareEnemyWhite.draw = function(self)
  10.   love.graphics.setColor(1, 1, 1)
  11.   love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
  12. end
  13. SquareEnemyRed = SquareEnemy:extend()
  14. SquareEnemyRed.new = function(self, ...)
  15.   SquareEnemyRed.super.new(self, ...)
  16.   self.hp = 200
  17. end
  18. SquareEnemyRed.draw = function(self)
  19.   love.graphics.setColor(1, 0, 0)
  20.   love.graphics.rectangle("fill", self.x, self.y, self.w, self.w)
  21. end
复制代码
定义工厂接口,在这里接口算是一种特殊的抽象类(由于只能用表来模拟接口,所以让接口也继承Objcet)
  1. IFactory = Object:extend()
  2. IFactory.circleEnemy = unrealized
  3. IFactory.squareEnemy = unrealized
  4. IFactory.barEnemy = unrealized
复制代码
分别实现白色工厂和红色工厂(如果没有额外的创建操作,可以不用return)
  1. WhiteFactory = Object:extend()
  2. WhiteFactory:implement(IFactory)
  3. WhiteFactory.circleEnemy = function(...)
  4.   return CircleEnemyWhite(...)
  5. end
  6. WhiteFactory.squareEnemy = function(...)
  7.   return SquareEnemyWhite(...)
  8. end
  9. WhiteFactory.barEnemy = function(...)
  10.   return BarEnemyWhite(...)
  11. end
  12. RedFactory = Object:extend()
  13. RedFactory:implement(IFactory)
  14. RedFactory.circleEnemy = function(...)
  15.   return CircleEnemyRed(...)
  16. end
  17. RedFactory.squareEnemy = function(...)
  18.   return SquareEnemyRed(...)
  19. end
  20. RedFactory.barEnemy = function(...)
  21.   return BarEnemyRed(...)
  22. end
复制代码
接下来测试抽象工厂
  1. require 'oo'
  2. require 'enemy.aac'
  3. require 'enemy.bar'
  4. require 'enemy.circle'
  5. require 'enemy.square'
  6. require 'factory.aac'
  7. require 'factory.red_factory'
  8. require 'factory.white_factory'
  9. enemies = {}
  10. love.load = function()
  11.   IFactory = WhiteFactory()
  12.   table.insert(enemies, IFactory.circleEnemy(100, 100, 25))
  13.   table.insert(enemies, IFactory.squareEnemy(100, 200, 25))
  14.   table.insert(enemies, IFactory.barEnemy(100, 300, 10, 50))
  15.   IFactory = RedFactory()
  16.   table.insert(enemies, IFactory.circleEnemy(200, 100, 25))
  17.   table.insert(enemies, IFactory.squareEnemy(200, 200, 25))
  18.   table.insert(enemies, IFactory.barEnemy(200, 300, 10, 50))
  19.   for _, enemy in pairs(enemies) do
  20.     print(enemy.hp)
  21.   end
  22. end
  23. love.draw = function()
  24.   for _, enemy in ipairs(enemies) do
  25.     enemy:draw()
  26.   end
  27. end
复制代码

参考资料


  • 《Lua程序设计·第四版》罗伯托·耶鲁萨林斯希 、第227~241页

其它

oo.lua
  1. Object = {}
  2. Object.__index = Objectfunction Object:new()
  3. endfunction Object:extend()
  4.   local cls = {}
  5.   for k, v in pairs(self) do
  6.     if k:find("__") == 1 then
  7.       cls[k] = v
  8.     end
  9.   end
  10.   cls.__index = cls
  11.   cls.super = self
  12.   setmetatable(cls, self)
  13.   return cls
  14. endfunction Object:implement(...)
  15.   for _, cls in pairs({ ... }) do
  16.     for k, v in pairs(cls) do
  17.       if self[k] == nil and type(v) == "function" then
  18.         self[k] = v
  19.       end
  20.     end
  21.   end
  22. endfunction Object:is(T)
  23.   local mt = getmetatable(self)
  24.   while mt do
  25.     if mt == T then
  26.       return true
  27.     end
  28.     mt = getmetatable(mt)
  29.   end
  30.   return false
  31. endfunction Object:__tostring()
  32.   return "Object"
  33. endfunction Object:__call(...)
  34.   local obj = setmetatable({}, self)
  35.   obj:new(...)
  36.   return obj
  37. endfunction unrealized(...)  error("未实现", 3)end-- return Object
复制代码
QUESTION1
如果不复制元方法,假设类B继承类A,类B的对象实例b,b的元表是类B,在调用 b + b 时,涉及到算术运算符相关的元方法,b会在父类B中查找__add,找不到并不会顺着B的元表__index再去B的父类A找,因此会报错
  1. A = {
  2.   __index = A,
  3.   __add = function(a, b)
  4.     return a.age + b.age
  5.   end,
  6.   name = "小白"
  7. }
  8. B = { __index = B, }
  9. b = { __index = b, age = 1 }
  10. setmetatable(B, A)
  11. setmetatable(b, B)
  12. print(b.name)
  13. print(b + b)
  14. --[[
  15. > dofile 'TEST/test.lua'
  16. 小白
  17. TEST/test.lua:15: attempt to perform arithmetic on a table value (global 'b')
  18. stack traceback:
  19.         TEST/test.lua:15: in main chunk
  20.         [C]: in function 'dofile'
  21.         stdin:1: in main chunk
  22.         [C]: in ?
  23. ]]
复制代码
点我返回


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

渣渣兔

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表