Lua オブジェクト指向

オブジェクト指向プログラミング(Object Oriented Programming、OOP)は非常に人気のあるコンピュータプログラミングのアーキテクチャであり、オブジェクトを作成・操作してアプリケーションを設計します。

以下のいくつかのプログラミング言語はすべてオブジェクト指向プログラミングをサポートしています。

  • C++
  • Java
  • Objective-C
  • Smalltalk
  • C#
  • Ruby

Lua は軽量なスクリプト言語です。Java や C++ のように強力なオブジェクト指向(OO)機能を内蔵しているわけではありませんが、非常に柔軟であり、いくつかのテクニックを使ってオブジェクト指向プログラミングを実現できます。


オブジェクト指向の特徴

  • カプセル化:データとメソッドをまとめ、実装の詳細を隠蔽し、必要なインターフェースだけを公開することで、安全性と保守性を高めます。

  • 継承:新しいクラスを派生させることで既存のコードを再利用・拡張し、重複コーディングを減らし、開発効率と拡張性を高めます。

  • ポリモーフィズム:同じ操作でも異なるオブジェクトに作用すると異なる動作を示し、統一されたインターフェースでの呼び出しをサポートし、柔軟性と拡張性を高めます。

  • 抽象化:複雑な問題を簡素化し、中核となるクラスとインターフェースを定義し、不要な詳細を隠すことで、複雑さの管理を容易にします。


Lua におけるオブジェクト指向

オブジェクトは属性とメソッドから構成されることを私たちは知っています。

Lua ではクラスはtable + functionシミュレートできます。

継承については、メタテーブルを使ってシミュレートできます(推奨されませんが、最も基本的なオブジェクトをシミュレートするだけなら大抵の実装で十分です)。

Lua では、最も基本的な構造は table であり、テーブル(table)を使ってオブジェクトを作成できます。

ClassName = {}  -- 创建一个表作为类

new メソッド(または他の名前)を使ってオブジェクトを作成し、オブジェクトの属性を初期化します。

function ClassName:new(...)
    local obj = {}  -- 创建一个新的空表作为对象
    setmetatable(obj, self)  -- 设置元表,使对象继承类的方法
    self.__index = self  -- 设置索引元方法
    -- 初始化对象的属性
    obj:init(...)  -- 可选:调用初始化函数
    return obj
end

テーブル(table)は Lua で最も基本的な複合データ型であり、オブジェクトの属性を表すために使えます。

Lua の function はメソッドを表すために使えます:

function ClassName:sayHello()
    print("Hello, my name is " .. self.name)
end

new メソッドを使ってオブジェクトを作成し、オブジェクトを通じてクラスのメソッドを呼び出します。

local obj = ClassName:new("Alice")  -- 创建对象
obj:sayHello()  -- 调用对象的方法

Lua では、テーブル(table)はオブジェクトの一種の変種とみなせます。オブジェクトと同様に、テーブルは状態(メンバ変数)を持ち、独立した実体を表すことができます。

テーブルはデータメンバだけでなく、オブジェクトのメソッドに似たメンバ関数も含むことができます:

例

-- Personクラスを定義
Person = {name = "", age = 0}

-- Personのコンストラクタ
function Person:new(name, age)
    local obj = {}  -- オブジェクトとして新しいテーブルを作成
    setmetatable(obj, self)  -- メタテーブルを設定し、Personのインスタンスにする
    self.__index = self  -- インデックスメタメソッドを設定し、Personを指すようにする
    obj.name = name
    obj.age = age
    return obj
end

-- メソッドを追加:個人情報を出力
function Person:introduce()
    print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end

コードの説明:

  • Personはテーブルであり、name と age という2つの属性を持ちます。この2つの属性はクラスのデフォルト属性です。
  • Person:new(name, age)はコンストラクタであり、新しい Person オブジェクトを作成するために使われます。
  • local obj = {}オブジェクトとして新しいテーブルを作成し、setmetatable(obj, self) でメタテーブルを設定して、そのテーブルを Person クラスのインスタンスにします。
  • self.__index = selfインデックスメタメソッドを設定することで、obj が Person クラスの属性とメソッドにアクセスできるようにします。
  • introduceは Person クラスのメソッドで、その Person オブジェクトの名前と年齢を出力します。

メソッドの呼び出し:

-- 创建一个 Person 对象
local person1 = Person:new("Alice", 30)

-- 调用对象的方法
person1:introduce()  -- 输出 "My name is Alice and I am 30 years old."

簡単な例

次の簡単なクラスには、area、length、breadth という3つの属性が含まれ、printArea メソッドは計算結果を出力するために使われます:

例

-- Rectangleクラスを定義
Rectangle = {area = 0, length = 0, breadth = 0}

-- Rectangleオブジェクトを作成するコンストラクタ
function Rectangle:new(o, length, breadth)
  o = o or {}  -- オブジェクトが渡されない場合、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Rectangleのメソッドを継承するようにする
  self.__index = self  -- アクセス時にメソッドと属性を見つけられるようにする
  o.length = length or 0  -- 長さを設定、デフォルトは0
  o.breadth = breadth or 0  -- 幅を設定、デフォルトは0
  o.area = o.length * o.breadth  -- 面積を計算
  return o
end

-- 長方形の面積を出力
function Rectangle:printArea()
  print("長方形の面積は ", self.area)
end

オブジェクトの作成

オブジェクトの作成は、クラスのインスタンスにメモリを割り当てるプロセスです。各クラスは自身のメモリを持ち、共通データを共有します:

r = Rectangle:new(nil,10,20)

属性へのアクセス

ドット記号を使用できます.クラスの属性にアクセスするには:

print(r.length)

メンバ関数へのアクセス

コロンを使用できます:クラスのメンバ関数にアクセスするには:

r:printArea()

メモリはオブジェクトの初期化時に割り当てられます。

完全な例

以下では、Lua のオブジェクト指向の完全な例を示します:

例

-- Rectangleクラスを定義
Rectangle = {area = 0, length = 0, breadth = 0}

-- Rectangleオブジェクトを作成するコンストラクタ
function Rectangle:new(o, length, breadth)
  o = o or {}  -- オブジェクトが渡されない場合、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Rectangleのメソッドを継承するようにする
  self.__index = self  -- アクセス時にメソッドと属性を見つけられるようにする
  o.length = length or 0  -- 長さを設定、デフォルトは0
  o.breadth = breadth or 0  -- 幅を設定、デフォルトは0
  o.area = o.length * o.breadth  -- 面積を計算
  return o
end

-- 長方形の面積を出力
function Rectangle:printArea()
  print("長方形の面積は ", self.area)
end

-- 実行例:
local rect1 = Rectangle:new(nil, 5, 10)  -- 長さ5、幅10の長方形を作成
rect1:printArea()  -- "長方形の面積は 50" と出力

local rect2 = Rectangle:new(nil, 7, 3)  -- 長さ7、幅3の長方形を作成
rect2:printArea()  -- "長方形の面積は 21" と出力

上記のプログラムを実行すると、出力結果は次のとおりです:

矩形面积为      50
矩形面积为      21

Lua の継承

継承とは、あるオブジェクトが別のオブジェクトの属性とメソッドを直接使用することであり、基底クラスの属性とメソッドを拡張するために使えます。

Lua の継承は、サブクラスのメタテーブルを設定することで実現されます。

新しいテーブルを作成し、そのメタテーブルを親クラスに設定することができます。

次の例では、Square クラスが Rectangle クラスの属性とメソッドを継承し、その上で変更を加えます。

-- Rectangleクラスを定義
Rectangle = {area = 0, length = 0, breadth = 0}

-- Rectangleオブジェクトを作成するコンストラクタ
function Rectangle:new(o, length, breadth)
  o = o or {}  -- オブジェクトが渡されない場合、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Rectangleのメソッドを継承するようにする
  self.__index = self  -- アクセス時にメソッドと属性を見つけられるようにする
  o.length = length or 0  -- 長さを設定、デフォルトは0
  o.breadth = breadth or 0  -- 幅を設定、デフォルトは0
  o.area = o.length * o.breadth  -- 面積を計算
  return o
end

-- 長方形の面積を出力
function Rectangle:printArea()
  print("長方形の面積は ", self.area)
end

-- Squareクラスを定義、Rectangleクラスを継承
Square = Rectangle:new()  -- SquareはRectangleクラスを継承

-- コンストラクタをオーバーライド(正方形の辺の長さは等しい)
function Square:new(o, side)
  o = o or {}  -- オブジェクトが渡されない場合、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Rectangleのメソッドを継承するようにする
  self.__index = self  -- アクセス時にメソッドと属性を見つけられるようにする
  o.length = side or 0  -- 辺の長さを設定
  o.breadth = side or 0  -- 正方形の幅と長さは等しい
  o.area = o.length * o.breadth  -- 面積を計算
  return o
end

-- 実行例:
local rect = Rectangle:new(nil, 5, 10)  -- 長さ5、幅10の長方形を作成
rect:printArea()  -- "長方形の面積は 50" と出力

local square = Square:new(nil, 4)  -- 辺の長さが4の正方形を作成
square:printArea()  -- "長方形の面積は 16" と出力

Rectangle 类:依然として長方形の基本クラスであり、次のものを持つlength、breadthとarea属性、および面積を計算して出力するメソッド。

Squareクラスは継承するRectangle:Squareクラスは...を通じてRectangle:new()継承するRectangleクラスのメソッドと属性を。正方形の縦と横の長さが等しいため、私たちはSquare:newメソッド内でコンストラクタをオーバーライドし、...をlengthとbreadth同じ値に設定します(すなわちside)。

コンストラクタのオーバーライド:Square:new(o, side)メソッドで正方形オブジェクトを作成するとき、渡された辺の長さを使用しますside初期化しlengthとbreadth属性を設定し、面積を計算します。

実行結果:

矩形面积为  50
矩形面积为  16

関数のオーバーライド

Lua では、関数のオーバーライド(メソッドのオーバーライドとも呼ばれます)とは、継承の過程で、サブクラスが親クラスに既にあるメソッドを再定義または置き換えることを指します。

サブクラスは必要に応じて親クラスのメソッドの動作を変更または拡張できます。

上記の例では、Square クラスが Rectangle クラスのコンストラクタをオーバーライドして、オブジェクトの初期化方法を変更しています。特に、正方形の特性は辺の長さが等しいことなので、矩形の length と breadth を同じ値に設定しています。

次に、Animal クラスとそれを継承する Dog クラスを使って、メソッドのオーバーライド方法を示します。

-- 動物クラス(Animal)を定義
Animal = {name = "Unknown"}

-- Animal クラスのコンストラクタ
function Animal:new(o, name)
  o = o or {}  -- オブジェクトが渡されない場合は、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Animal のメソッドを継承するようにする
  self.__index = self  -- オブジェクトが Animal のメソッドにアクセスできるようにする
  o.name = name or "Unknown"  -- 名前を設定、デフォルトは "Unknown"
  return o
end

-- Animal クラスのメソッド:鳴き声
function Animal:speak()
  print(self.name .. " makes a sound.")
end


-- 犬クラス(Dog)を定義。Animal を継承
Dog = Animal:new()  -- Dog は Animal クラスを継承

-- 犬クラスのコンストラクタをオーバーライド
function Dog:new(o, name, breed)
  o = o or {}  -- オブジェクトが渡されない場合は、新しい空のテーブルを作成
  setmetatable(o, self)  -- メタテーブルを設定し、Dog と Animal のメソッドを継承するようにする
  self.__index = self  -- オブジェクトが Dog のメソッドにアクセスできるようにする
  o.name = name or "Unknown"
  o.breed = breed or "Unknown"
  return o
end

-- 犬クラスの鳴き声メソッドをオーバーライド(Animal の speak メソッドをオーバーライド)
function Dog:speak()
  print(self.name .. " barks.")
end


-- Animal オブジェクトを作成
local animal = Animal:new(nil, "Generic Animal")
animal:speak()  -- "Generic Animal makes a sound." を出力

-- Dog オブジェクトを作成
local dog = Dog:new(nil, "Buddy", "Golden Retriever")
dog:speak()  -- "Buddy barks." を出力
  • Animal 类:基本クラスを定義しましたAnimal、name属性とspeakメソッドを持ちます。speakメソッドはデフォルトの実装であり、「何らかの動物が音を出す」と出力します。

  • Dogクラスの継承Animal:Dogクラスが継承するAnimal、そしてDog:new()メソッドで自身のインスタンスを作成します。

  • オーバーライドspeakメソッド:Dogクラス内で、オーバーライドしましたspeakメソッドは、その動作を親クラスの「音を出す」から「ワンと吠える」に変更しました。これがメソッドオーバーライドの表れであり、サブクラス(Dog)が親クラス(Animal)のメソッドの動作を変更しました。

実行結果:

Generic Animal makes a sound.
Buddy barks.

ポリモーフィズム

Lua のポリモーフィズムは、メタテーブルとメソッドのオーバーライドによって実現されます。異なる型のオブジェクトが同じメソッドを呼び出すと、Lua はオブジェクトの実際の型に応じて異なるメソッドを実行します。

例

-- 「クラス」(実際にはテーブル)を定義
Person = {}

-- 「クラス」にコンストラクタを追加
function Person:new(name, age)
    local obj = {}  -- 新しいテーブルをオブジェクトとして作成
    setmetatable(obj, self)  -- メタテーブルを設定し、Person クラスのインスタンスであることを示す
    self.__index = self  -- インデックス用メタメソッドを設定し、Person を指すようにする
    obj.name = name
    obj.age = age
    return obj
end

-- メソッドを追加
function Person:greet()
    print("Hello, my name is " .. self.name)
end

-- サブクラス Student を定義し、Person を継承
Student = Person:new()

-- サブクラスが親クラスのメソッドをオーバーライド
function Student:greet()
    print("Hi, I'm a student and my name is " .. self.name)
end

local person2 = Person:new("Charlie", 25)
local student2 = Student:new("David", 18)

-- ポリモーフィズム:異なる型のオブジェクトが同じメソッドを呼び出す
person2:greet()  -- "Hello, my name is Charlie" を出力
student2:greet()  -- "Hi, I'm a student and my name is David" を出力

person2 と student2 は同じ greet メソッドを呼び出していますが、型が異なるため、Lua はそれぞれに適したバージョンを呼び出します。

実行結果:

Hello, my name is Charlie
Hi, I'm a student and my name is David

その他のオブジェクト指向の概念

カプセル化

カプセル化は通常、データとメソッドを1つのテーブルにカプセル化することで実現します。メタメソッドを使用して外部からのアクセスを制限するなど、テーブルへのアクセス権限を制御することで、カプセル化をシミュレートできます。

例

-- 「クラス」(実際にはテーブル)を定義
Person = {}

-- カプセル化を追加:属性を隠す
function Person:new(name, age)
    local obj = {}
    setmetatable(obj, self)
    self.__index = self
    obj.name = name
    obj.age = age
    return obj
end

function Person:setName(name)
    self.name = name  -- name を変更するメソッドを提供
end

function Person:getName()
    return self.name  -- name を取得するメソッドを提供
end

この方法により、属性へのアクセスを制御し、カプセル化をシミュレートできます。

抽象化

抽象化とは、複雑なものを簡素化し、不要な詳細を隠すことです。Lua 自体にはクラスの概念はありませんが、カプセル化によって抽象化を実現できます。

例

-- インターフェースのみを公開し、実装の詳細は公開しない
function Person:showInfo()
    print("Name: " .. self.name)
    print("Age: " .. self.age)
end
その他の拡張