【Lua学習記】Lua Advanced - 表(4) 継承、カプセル化、ポリモーフィズム

ここに画像の説明を挿入します


カプセル化

// 定义基类
Object = {
    
    }

//由于表的特性,该句就相当于定义基类变量
Object.id =1

//该句相当于定义方法,Object可以视为定义的对象,Test可以视为方法名
//我们知道Object是一个表,但是抽象地看,请把Object看着面向对象中的 “对象”
function Object:Test()
    print(self.id)
end
// 以上语句等同于:
// public class Object{
    
    
	int id=1;
	void Test(Object obj)
		print(obj.id);
}


//定义一个new方法,用于创建这个基类的对象
function Object:new()
	//定义空表obj,用面向对象比喻相当于new了一个空对象
    local obj = {
    
    }
    //绑定元表,将元表看作一个基类
    self.__index = self
    setmetatable(obj, self)
    //返回空对象
    return obj
end

local Car = Object:new()  //实际是将new出的空对象return给外部定义的Car
// 以上语句等同于:
// Object Car = new Object();

// 由于Car实际上是空的table,所以访问Car其实是通过__index访问基类中的索引
// 相当于虽然没有定义Car内的变量,但初始化时继承基类的值作为了初始值
print(Car.id) --1,来自元表

// 同样的,Car实际使用了__index基类提供的方法
// 但是由于入参是self,此处就是Car,print(Car.id),最终还是访问了基类__index找到的Object.id
Car:Test() --1,来自元表

// 定义Car中的变量
Car.id = 2
// 现在Car表中有了索引id,那么就能找到这个索引,所以输出为2
Car:Test() --2,来自子表

これで、オブジェクト指向と同じように、基本クラスに対応する新しいオブジェクトを作成できるようになります。ただし、ここでの new はオブジェクト指向の new と完全に似ているわけではありません。

Car.name = "a"
print(Car.name)
输出:
a

Object クラスをカプセル化するとき、名前のインデックスはまったくありません。Lua では、新しいオブジェクトを作成し、いくつかの新しい変数とメソッドを追加します。これらの機能は、明らかに、親クラスを継承するサブクラスでのみ利用できます。それは悪いことではありませんが、完全なカプセル化が必要な場合は、いくつかの制限を課すことができます。

//定义一个垃圾列表,将添加到子类的垃圾都丢进去
garbage={
    
    }

//定义一个new方法,用于创建这个基类的对象
function Object:new()
	//定义空表obj,用面向对象比喻相当于new了一个空对象
    local obj = {
    
    }
    // 禁止子类的添加
    self.__newindex = garbage
    //绑定元表,将元表看作一个基类
    self.__index = self
    setmetatable(obj, self)
    //返回空对象
    return obj
end
local Car = Object:new()
Car.name = "a"
print(Car.name)

输出:
nil

これで、基本クラスのメソッドと変数にアクセスできるだけでなく、他の新しく追加されたものを防ぐこともできるカプセル化が実際に実装されました。ただし、ガベージは時間内にクリーンアップする必要があります。これについては、後のガベージ コレクションで説明します。


継承する

継承はオブジェクト指向の重要な機能です。新しいオブジェクトだけではすべてのニーズを満たすことはできません。親クラスのメソッドを直接使用するのではなくオーバーライドしたい場合は、継承が必要です。

上記のObject:new()コードを観察すると、実際、それを継承に使用したい場合は、コードを変更するだけで済みます。

Object = {
    
    }
Object.id = 1;
function Object:Test()
    print(self.id)
end

//换种方式,如果我们不return的话,想要返回这个值,可以直接把它丢进全局表中
function Object:subClass(className)
    _G[className] = {
    
    }
    self.__index = self
    setmetatable(_G[className], self)
end
Object:subClass("Cat")
print(Cat.id)
输出:
1

継承はカプセル化よりも少し単純で、実際、最初に定義したカプセル化とまったく同じですが、実装方法が異なります。

// new一个Cat类的对象
local WhiteCat = Cat:new()
print(WhiteCat.id) -- 1
function Object:Test()
    print("我是基类")
end

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

function Object:subClass(className)
    _G[className] = {
    
    }
    self.__index = self
    setmetatable(_G[className], self)
end

//Cat继承基类
Object:subClass("Cat")
//new一个Cat类的对象WhiteCat
local WhiteCat = Cat:new()
WhiteCat:Test()  -- 我是基类

// 重写Test方法(其实只是新写了一个放在Cat表里被调用,更像重载?)
function Cat:Test()
    print("我是猫类")
end
WhiteCat:Test()  --我是猫类

//想要重写Cat的Test方法?不好意思我已经用__newindex封装好了
//白猫是个对象,而不是Cat这个类,它不应该重写方法
//下面重写的方法会被丢到garbage里
function WhiteCat:Test()
    print("我是白猫")
end
WhiteCat:Test() --我是猫类
garbage:Test() --我是白猫

理解できない場合は、テーブル、メタテーブル、オブジェクト指向を学び直すことをお勧めします


ポリモーフィズム

ポリモーフィズムとは、親クラスの同じメソッドに対して、サブクラスが異なるロジックを実行できることを意味します。ポリモーフィズムを実現するにはどうすればよいでしょうか?

  1. メソッドのオーバーライドとオーバーロード
  2. インターフェースを実装する
  3. 抽象クラスと抽象メソッドを実装する

書き換えると次のようになります。

function Object:Test()
    print("我是基类")
end
Object:subClass("Cat")
Object:subClass("Dog")
function Cat:Test()
    print("我是猫类")
end
function Dog:Test()
    print("我是狗?")
end

書き換えは可能ですが、親クラスを継承して書き換えると親クラスの同名のメソッドを保持できないことが問題で、親クラスのメソッドにアクセスしたい場合はどうすればよいでしょうか。

このクラスは実際にはテーブルであることを忘れないでください。親クラスをテーブルに直接格納し、使用したいときにアクセスすることはできないでしょうか? いずれにせよ、オブジェクト指向構文の制限はありません。

function Object:subClass(className)
    _G[className] = {
    
    }
    local obj = _G[className]
    self.__index = self
    // 直接把父类表存进子类的base
    obj.base = self
    setmetatable(obj , self)
end

function Dog:Test()
    print("我是狗?")
end
Dog:Test()
Dog.base:Test()

输出:
我是狗?
我是基类

function Dog:Test()
	// 如果想在继承了父类的方法的基础之上重写
    self.base:Test()
    print("我是狗?")
end

Dog:Test() --我是基类 我是狗?

親クラスのメソッドを直接使用する場合は、親クラスを呼び出すときに異なるクラスがグローバル変数を共有するのを避ける必要があることに注意してください。

Object = {
    
    }
Object.id = 1;
function Object:Test()
    self.id = self.id + 1
    print(self.id)
end

Object:subClass("Cat")
function Cat:Test()
	self.base:Test()
    print("我是猫类")
end
Object:subClass("Dog")
function Dog:Test()
    self.base:Test()
    print("我是狗?")
end

输出:
2
我是猫类
3
我是狗?

理由も非常に単純で、table親クラスはメモリ上に格納されているのでtable親クラスのメソッドを直接呼び出すと呼び出し回数が1回増えることになります。両者の親クラスは同じアドレスを持ち、これがこのメソッドで毎回渡されるもの、つまり親クラスそのものなので追加されるものは親クラスにあります。一定、増加。Testself.idtableObject:Test()selfxxx.basetableself.idid

Object:Test()したがって、親クラスのメソッドの継承に基づいてサブクラスを書き換えられるようにし、メソッドを使用するサブクラスのテーブルもself.id変更したい場合は、次のように記述する必要があります。self

Object = {
    
    }
Object.id = 1;
function Object:Test()
    self.id = self.id + 1
    print(self.id)
end

Object:subClass("Cat")
function Cat:Test()
	// 手动地传入参数,因为冒号传入给self的是base
	// 因此需要手动地改变传入的参数的值
	self.base.Test(self)
    print("我是猫类")
end
Cat:Test()

输出:
2
我是猫类

オーバーロードは、関数の入力パラメーターの数を変更するだけで済む、最も単純なポリモーフィックなメソッドです。

インターフェイスと抽象クラスに関しては、Lua 独自の関数を書き直すことができ、抽象化は依然として非常に強力です。インターフェースについては、別のテーブル構造にアクセスして実装できるようにする必要があります(例えば、self.baseをインターフェースとみなすべきです) もちろん、これは私の考えであり、まだ学習していません。

おすすめ

転載: blog.csdn.net/milu_ELK/article/details/131978479