Mixinパターン
複数の元テーブルからメソッドをコピーして機能を組み合わせます。
「Mixinパターン」はCoddyKit上の無料Lua Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLua Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Lua Academyコースには全4レッスンが含まれています。
Mixinとは
Mixinは、複数のクラスにコピーできるメソッドのテーブルです。継承とは異なり、Mixinは親子関係を構築せずに振る舞いを追加します。1つのクラスで複数のMixinの振る舞いを組み合わせることもできます。
local Flyable={}
function Flyable:fly()
print(self.name.." is flying!")
end
function Flyable:land()
print(self.name.." landed.")
end
local Bird={} Bird.__index=Bird
-- Mix in Flyable
for k,v in pairs(Flyable) do Bird[k]=v end
function Bird.new(name)
return setmetatable({name=name},Bird)
end
Bird.new("Eagle"):fly()複数のMixin
1つのクラスに複数のMixinを適用すると、単純な部品を組み合わせて複雑な振る舞いを構成できます。これは水平合成と呼ばれ、それぞれのMixinは独立しています。
local Serializable={}
function Serializable:toCSV(fields)
local row={}
for _,f in ipairs(fields) do row[#row+1]=tostring(self[f]) end
return table.concat(row,",")
end
local Loggable={}
function Loggable:log(msg)
print("["..os.date("%H:%M").."]["..self.name.."] "..msg)
end
local Entity={} Entity.__index=Entity
for k,v in pairs(Serializable) do Entity[k]=v end
for k,v in pairs(Loggable) do Entity[k]=v end
function Entity.new(n,v) return setmetatable({name=n,value=v},Entity) end
local e=Entity.new("item",42)
e:log("created"); print(e:toCSV({"name","value"}))Mixinの競合
2つのMixinが同じメソッド名を定義している場合、後から適用したMixinが優先されます。どちらのMixinの実装を残すか明示的に選ぶか、両方を呼び出すようにラップして競合を解決します。
local A={speak=function(self) print(self.name.." (A)") end}
local B={speak=function(self) print(self.name.." (B)") end}
local C={} C.__index=C
for k,v in pairs(A) do C[k]=v end
for k,v in pairs(B) do C[k]=v end -- B.speak overwrites A.speak
-- Explicit resolution:
local origA, origB = A.speak, B.speak
function C:speak() origA(self); origB(self) end -- call both
local c=setmetatable({name="X"},C)
c:speak()include()ヘルパー
ヘルパー関数を使うと、Mixinの適用を簡潔に記述できます。include(Class, Mixin)はMixinのメソッドをクラスにコピーしますが、すでに存在するフィールドはスキップするため、既存のメソッドは上書きされません。
local function include(cls, mixin)
for k, v in pairs(mixin) do
if cls[k] == nil then
cls[k] = v
end
end
return cls
end
local Comparable={
equals=function(a,b) return a:compareTo(b)==0 end,
lt=function(a,b) return a:compareTo(b)<0 end,
}
local Temp={} Temp.__index=Temp
include(Temp, Comparable)
function Temp.new(c) return setmetatable({c=c},Temp) end
function Temp:compareTo(other) return self.c-other.c end
local a,b=Temp.new(100),Temp.new(50)
print(b.lt and b:lt(a)) -- true (b < a)状態を持つMixin
インスタンスごとの状態が必要なMixinでは、Mixinのinit関数で状態を初期化します。クラスのコンストラクターはMixinのinit関数を呼び出し、各Mixinの状態をインスタンスに設定します。
local Observable={}
function Observable._init(self)
self._listeners={}
end
function Observable:on(event,fn)
self._listeners[event]=self._listeners[event] or {}
self._listeners[event][#self._listeners[event]+1]=fn
end
function Observable:emit(event,...)
for _,fn in ipairs(self._listeners[event] or {}) do fn(...) end
end
local Button={} Button.__index=Button
for k,v in pairs(Observable) do Button[k]=v end
function Button.new(label)
local self=setmetatable({label=label},Button)
Observable._init(self)
return self
end
local btn=Button.new("OK")
btn:on("click",function() print("Clicked!") end)
btn:emit("click")Mixinモジュール
Mixinの振る舞いを個別のモジュールファイルにまとめます。クラスのモジュールでそれらをRequireし、適用します。これにより、それぞれの関心事を専用のファイルに分離でき、コードベースを整理できます。
-- In a real project:
-- local Loggable = require("mixins.loggable")
-- local Cacheable = require("mixins.cacheable")
-- local MyClass = {}
-- for k,v in pairs(Loggable) do MyClass[k]=v end
-- for k,v in pairs(Cacheable) do MyClass[k]=v end
local TimestampMixin={}
function TimestampMixin:setCreated() self.createdAt=os.time() end
function TimestampMixin:age() return os.time()-self.createdAt end
local Record={} Record.__index=Record
for k,v in pairs(TimestampMixin) do Record[k]=v end
function Record.new(data)
local self=setmetatable(data or {},Record)
self:setCreated()
return self
end
print(Record.new({}).createdAt>0)役割ベースのMixin
Mixin関数を選択的に適用することで、実行時にオブジェクトへ役割を割り当てられます。これにより、クラス単位ではなくインスタンス単位で振る舞いを設定できます。
local Roles={}
Roles.admin={deleteUser=function(self) print(self.name.." deletes user") end}
Roles.editor={editPost=function(self) print(self.name.." edits post") end}
Roles.viewer={viewPost=function(self) print(self.name.." views post") end}
local User={} User.__index=User
function User.new(name,...)
local self=setmetatable({name=name},User)
for _,role in ipairs({...}) do
for k,v in pairs(Roles[role] or {}) do self[k]=v end
end
return self
end
local alice=User.new("Alice","admin","editor")
alice:deleteUser(); alice:editPost()Mixinの適用状況の確認
setを使って、クラスに適用されたMixinを記録します。これにより、同じMixinの二重適用を防ぎ、クラスが持つ機能を調べられます。
local function applyMixin(cls, mixin, name)
cls._mixins = cls._mixins or {}
if cls._mixins[name] then return end -- already applied
for k,v in pairs(mixin) do
if k~="_init" then cls[k]=cls[k] or v end
end
cls._mixins[name] = true
end
local Fly={fly=function(self) print(self.name.." flies") end}
local Bird={} Bird.__index=Bird
applyMixin(Bird,Fly,"Fly")
applyMixin(Bird,Fly,"Fly") -- no-op, already applied
print(Bird._mixins.Fly) -- trueMixinと継承とコンポジション
構成の方法には、継承(is-a、厳密な階層構造)、Mixin(メソッドのコピー、フラットな構造)、コンポジション(has-a、委譲)の3つがあります。関係性に基づいて選択します。動物 IS-A 生物なら継承、生物 CAN fly ならMixin、車 HAS engine ならコンポジションです。
-- Inheritance: "is-a"
-- local Dog = extends(Animal)
-- Mixin: "can-do"
-- include(Dog, Swimmable)
-- Composition: "has-a"
-- local Dog = {}
-- function Dog.new()
-- return {collar=Collar.new(),chip=Chip.new()}
-- end
print("Pick the right tool for the relationship")Mixinのテンプレート
標準的なMixinのテンプレートでは、Mixinのテーブルにメソッド、インスタンスごとの状態に使うオプションの_init、競合検出用のnameフィールドを定義します。一貫したパターンに従うと、プロジェクト間でMixinを組み合わせやすくなります。
-- Standard mixin template:
local EventMixin = {}
EventMixin._name = "EventMixin"
function EventMixin._init(self)
self._events = {}
end
function EventMixin:on(e, fn)
self._events[e] = self._events[e] or {}
self._events[e][#self._events[e]+1] = fn
end
function EventMixin:trigger(e, ...)
for _, fn in ipairs(self._events[e] or {}) do fn(...) end
end
-- return EventMixin
print("EventMixin ready for use")Mixinに関する質問
クラスにMixinを適用するにはどうしますか?
まとめ:Mixinのパターン
Mixinは、継承を使わずに振る舞いを構成するため、ソーステーブルから対象クラスへメソッドをコピーします。シリアライゼーション、ロギング、イベント処理など、複数の場所にまたがる関心事に使用します。
よくある質問
「Mixinパターン」レッスンは無料ですか?
はい。「Mixinパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Lua Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Lua Academyコースには全4レッスンが含まれています。
「Mixinパターン」で何を学びますか?
複数の元テーブルからメソッドをコピーして機能を組み合わせます。 ブラウザで直接実行するハンズオンコードでLua Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Lua Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのLua Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「Mixinパターン」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このLua Academyレッスンでコードを書いて実行できますか?
はい。すべてのLua Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。