Lua Academy · Oppitunti

Mixin-rakenteet

Koostakaa toiminnallisuutta kopioimalla metodeja useista lähdetaulukoista.

Oppitunti 3/412 vaihetta

Mixin-rakenteet on ilmainen Lua Academy-oppitunti CoddyKitissä. Tämä on oppitunti 3/4. Voit lukea tästä oppimispolusta kokonaan mitkä tahansa 3 oppituntia ilmaiseksi — sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä käytännön harjoittelun sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Oppitunti kuuluu Lua Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Lua Academy-kurssilla on yhteensä 4 oppituntia.

Mikä on mixin?

Mixin on metodien taulukko, joka voidaan kopioida useisiin luokkiin. Toisin kuin periytyminen, mixinit lisäävät toiminnallisuutta muodostamatta isäntä–lapsi-suhdetta. Luokka voi yhdistää useiden mixinien toiminnallisuuksia.

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()

Useita mixinejä

Käytä yhdessä luokassa useita mixinejä ja kokoa yksinkertaisista osista monimutkaista toiminnallisuutta. Tätä kutsutaan horisontaaliseksi koostamiseksi — kukin mixin on itsenäinen.

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"}))

Mixinien ristiriidat

Kun kaksi mixiniä määrittelee samannimisen metodin, viimeksi käytetty voittaa. Ratkaise ristiriidat eksplisiittisesti valitsemalla säilytettävä versio tai käärimällä metodit niin, että molempia kutsutaan.

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()-apu

Apufunktio tekee mixinien käyttöönotosta selkeämpää: include(Class, Mixin) kopioi mixinin metodit luokkaan ja ohittaa jo olemassa olevat kentät, joten olemassa olevia metodeja ei korvata.

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)

Tilallinen mixin

Mixin, joka tarvitsee oliokohtaista tilaa, alustaa sen mixinin init-funktiossa. Luokan konstruktori kutsuu mixinien init-funktioita ja määrittää kunkin mixinin tilan oliolle.

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-moduulit

Paketoi mixinin toiminnallisuus erillisiin moduulitiedostoihin. Vaadi ne luokan moduulissa ja ota ne käyttöön. Näin kukin toiminnallisuusalue pysyy omassa tiedostossaan ja koodikanta säilyy järjestelmällisenä.

-- 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)

Roolipohjaiset mixinit

Määritä olioille rooleja suorituksen aikana ottamalla mixin-funktioita käyttöön valikoivasti. Näin toiminnallisuus voidaan määrittää oliokohtaisesti luokkakohtaisuuden sijaan.

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()

Mixinien käyttöönoton tarkistaminen

Seuraa joukon avulla, mitkä mixinit luokkaan on otettu käyttöön. Tämä estää saman mixinin käyttöönoton kahdesti ja mahdollistaa luokan ominaisuuksien tarkastelun.

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)  -- true

Mixinit, periytyminen ja koostaminen

Kolme koostamistapaa: periytyminen (on-tyyppinen suhde, tiukka hierarkia), mixinit (metodien kopiointi, tasainen rakenne) ja koostaminen (sisältää-suhde, delegointi). Valitse tapa suhteen perusteella: eläin ON olento (periytyminen), olento PYSTYY lentämään (mixin), auto SISÄLTÄÄ moottorin (koostaminen).

-- 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-mallipohja

Mixinien vakiomallissa mixin-taulukko sisältää metodit, valinnaisen _init-funktion oliokohtaista tilaa varten sekä nimikentän ristiriitojen tunnistamista varten. Yhtenäisen mallin noudattaminen tekee mixineistä yhdisteltäviä eri projekteissa.

-- 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-kysymys

Miten mixin otetaan käyttöön luokassa?

Kertaus: mixin-mallit

Mixinit kopioivat metodit lähdetaulukosta kohdeluokkaan ja koostavat toiminnallisuutta ilman periytymistä. Käytä niitä poikittaisten huolenaiheiden, kuten sarjallistamisen, lokituksen ja tapahtumankäsittelyn, toteuttamiseen.

Aloita maksutta

Opi Lua tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
40
Oppitunnit
159

Usein kysytyt kysymykset

Onko oppitunti ”Mixin-rakenteet” ilmainen?

Kyllä — voit lukea täällä verkossa kokonaan ilmaiseksi mitkä tahansa Lua Academy-oppimispolun 3 oppituntia, myös oppitunnin “Mixin-rakenteet”. Sen jälkeen CoddyKit PRO avaa kaikki oppitunnit sekä interaktiiviset harjoitukset sisäänrakennetulla koodieditorilla ja ympäri vuorokauden toimivalla tekoälytuutorilla. Lua Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Mixin-rakenteet”?

Koostakaa toiminnallisuutta kopioimalla metodeja useista lähdetaulukoista. Harjoittelet Lua Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Lua Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Lua Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 3/4.

Kuinka kauan ”Mixin-rakenteet”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Lua Academy-oppitunnilla?

Kyllä. Jokainen Lua Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Yksiperintä __index-ketjutuksella
  2. Emoluokan metodien kutsuminen superilla
  3. Mixin-rakenteet
  4. Ylikirjoittaminen ja polymorfismi
← Takaisin: Lua Academy