Objektově orientované programování v Lua

Pavel Tišnovský 28. 4. 2009

V osmé části seriálu o programovacím jazyce Lua si ukážeme složitější příklad, v němž použijeme volání céčkových funkcí ze skriptů psaných v Lua, včetně kontroly počtu a typů parametrů. Dále si vysvětlíme, jakým způsobem lze v jazyku Lua psát aplikace s využitím objektově orientovaného programování.

Obsah

1. Demonstrační příklad – podpora bitmapové grafiky pro Lua skripty
2. Testovací skripty využívající rozhraní pro bitmapovou grafiku
   2.1 Vykreslení jednoduchého obrazce založeného na aliasu
   2.2 Vykreslení Mandelbrotovy množiny
   2.3 Napodobení demo efektu „plasmy“
   2.4 RGB spirála
   2.5 Animace – amplitudová modulace (AM)
3. Objektově orientované programování v jazyku Lua
4. Vlastnosti asociativních polí
5. Vytvoření objektu pomocí uzávěru
6. Deklarace metod vně konstruktoru
7. Alternativní způsob zápisu metod
8. Odkazy na Internetu
9. Obsah další části seriálu

1. Demonstrační příklad – podpora bitmapové grafiky pro Lua skripty

V předchozí části seriálu o programovacím jazyce Lua jsme si ukázali, jakým způsobem je možné v aplikaci využívající interpretr Lua provést registraci céčkové funkce tak, aby ji bylo možné volat z Lua skriptů. Také jsme si popsali některé funkce aplikačního programového rozhraní (API) interpretru, jenž slouží k získání parametrů předávaných z Lua skriptu céčkové funkci, včetně kontroly počtu skutečně předaných parametrů a jejich typu. Tyto znalosti využijeme v dnešním demonstračním příkladu, který je poněkud rozsáhlejší, než příklady předchozí. Jedná se o aplikaci se zabudovaným interpretrem jazyka Lua, který je rozšířený o několik funkcí sloužících pro tvorbu bitmapových (rastrových) obrázků ukládaných do souborů typu PPM – Portable Pixel Map. Tento příklad je – po dalším rozšíření, zejména přidání podpory kreslení čar, výplní a textů – využitelný pro výuku základů počítačové grafiky (ostatně právě z tohoto důvodu pro potřeby škol vznikl). V aplikaci jsou zaregistrovány čtyři céčkové funkce, které je možné z Lua skriptů volat:

Název funkce Parametry Význam
createBitmap width, height Vytvoření bitmapy se zadanou šířkou a výškou
clearBitmap Vymazání celé bitmapy černou barvou
saveBitmap filename Uložení bitmapy do externího souboru se specifikovaným jmé­nem
putpixel x, y, r, g, b Vybarvení pixelu na souřadnicích [x,y] barvou [r,g,b]

Vzhledem k tomu, že se jedná o aplikaci navrženou pro školní prostředí s relativně omezenou výkonností počítačů (popis některých počítačových učeben s darovanými počítači a způsob jejich administrace by vydal na samostatný článek), je možné pracovat vždy pouze s jednou bitmapou, jejíž maximální rozměry jsou 1024×1024 pixelů, což při formátu uložení 24 bitů na pixel představuje oblast paměti o velikosti 3 MB. Při volání všech čtyř zmíněných funkcí se kontroluje jak počet parametrů, tak i jejich typ a rozsah – například souřadnice vybarvovaného pixelu musí ležet uvnitř rastrového obrázku. V případě, že parametry nejsou zadány korektně či dojde k jinému chybovému stavu (pokus o uložení bitmapy, která ještě nebyla vytvořena atd.), je na zásobník interpretru jazyka Lua uložena chybová zpráva, skript je ukončen a následně je vypsán aktuální stav zásobníku, který obsahuje mj. i chybovou zprávu (může se samozřejmě jednat i o zprávu vytvořenou vlastním interpretrem, například ve chvíli, kdy skript obsahuje syntaktickou chybu).

Zdrojový kód aplikace má velikost cca 6 kB, přičemž pro zjednodušení zápisu programu je využíváno několik maker (LUA_ERROR, NUMBER_OF_PARA­METERS, NUMBERP, CHECK_RANGE a další), takže přidání a registrace dalších céčkových funkcí pro práci s rastrovou grafikou je snadné:

/*
 * Čtvrtý demonstrační příklad
 *
 * Základní podpora pro práci s bitmapovou grafikou v jazyce Lua.
 *
 * Autor: Pavel Tišnovský, 2009, lze šířit v souladu s GPL
 */

#include <stdlib.h>
#include <stdio.h>
#include <mem.h>
#include <lauxlib.h>
#include <lualib.h>

/* Maximální povolené rozměry bitmapy */
#define MAX_BITMAP_WIDTH 1024
#define MAX_BITMAP_HEIGHT 1024

/* Povolení kontroly rozsahu barvových komponent pixelu */
/*#define CHECK_COLOR_COMPONENTS*/

/* Makro, které na zásobník uloží zprávu o chybě */
#define LUA_ERROR(errorMessage) {lua_pushstring(L, (errorMessage)); lua_error(L);}

/* Makro pro kontrolu počtu parametrů */
#define NUMBER_OF_PARAMETERS(cnt) if (lua_gettop(L)!=(cnt)) LUA_ERROR("incorrect number of parameters")

/* Makro pro kontrolu, zda je parametr specifikovaný indexem, typu číslo */
#define NUMBERP(index) if (!lua_isnumber(L, (index))) LUA_ERROR("type mishmash - number expected")

/* Makro pro kontrolu, zda je parametr specifikovaný indexem, typu řetězec */
#define STRINGP(index) if (!lua_isstring(L, (index))) LUA_ERROR("type mishmash - string expected")

#define CHECK_RANGE(x, min, max, errorMessage) if ((x)<(min) || (x)>(max)) LUA_ERROR(errorMessage)

#define LUA_FUNC static int
#define LUA_OK return 0;

/* Struktura popisující bitmapu */
typedef struct
{
    unsigned int width;
    unsigned int height;
    unsigned char *array;
    unsigned long size;
} Bitmap;

Bitmap *bmp = NULL;

/*
 * Vytvoření bitmapy s 24bpp (truecolor)
 */
Bitmap * bitmapCreate(unsigned int width, unsigned int height)
{
    Bitmap *bitmap=(Bitmap*)malloc(sizeof(Bitmap));
    if (bitmap == NULL)
    {
        return NULL;
    }
    bitmap->width=width;
    bitmap->height=height;
    bitmap->size=3*width*height;
    bitmap->array=(unsigned char*)malloc(bitmap->size*sizeof(unsigned char));
    if (bitmap->array == NULL)
    {
        free(bitmap);
        return NULL;
    }
    return bitmap;
}

/*
 * Dealokace paměti s bitmapou
 */
void bitmapDestroy(Bitmap *bitmap)
{
    if (!bitmap || !bitmap->array)
    {
        return;
    }
    free(bitmap->array);
    free(bitmap);
}

/*
 * Vymazání obsahu bitmapy
 */
void bitmapClear(Bitmap *bitmap)
{
    if (!bitmap || !bitmap->array)
    {
        return;
    }
    memset(bitmap->array, 0x00, bitmap->size);
}

/*
 * Vykreslení jednoho pixelu do bitmapy. Pixel je zadaný
 * svými souřadnicemi a barvou v barvovém prostoru RGB.
 */
void bitmapPutPixel(Bitmap *bitmap, int x, int y, unsigned char r, unsigned char g, unsigned char b)
{
    unsigned char *p;
    if (!bitmap || !bitmap->array)
    {
        return;
    }
    if (x<0 || y<0 || x>=bitmap->width || y>=bitmap->height)
    {
        return;
    }
    p=bitmap->array+(3*(x+y*bitmap->width));
    *p++=r;
    *p++=g;
    *p=b;
}

/*
 * Uložení bitmapy do souboru typu PPM (Portable PixelMap)
 */
int bitmapSave(Bitmap *bitmap, const char *name)
{
    FILE *fout = fopen(name, "wb");
    if (!bitmap || !bitmap->array)
    {
        return 0;
    }
    if (fout != NULL)
    {
        int result = 1;
        /* Kontrola zápisu hlavičky */
        result &= (fprintf(fout, "P6\n"\
               "# Created by Lua script\n"
               "%d %d\n255\n", bitmap->width, bitmap->height) > 0);
        /* Kontrola zápisu vlastní bitmapy */
        result &= (fwrite(bitmap->array, bitmap->size, 1, fout) == 1);
        /* Kontrola uzavření souboru */
        result &= (fclose(fout) == 0);
        return result;
    }
    return 0;
}

/*
 * Funkce volaná ze skriptu
 */
LUA_FUNC createBitmap(lua_State* L)
{
    int width, height;
    if (bmp)
    {
        LUA_ERROR("bitmap is already created");
    }

    /* Kontrola počtu parametrů */
    NUMBER_OF_PARAMETERS(2);
    /* Kontrola typu parametrů */
    NUMBERP(1);
    NUMBERP(2);
    width = lua_tointeger(L, 1);
    height = lua_tointeger(L, 2);
    /* Kontrola hodnot parametrů */
    CHECK_RANGE(width, 0, MAX_BITMAP_WIDTH, "bitmap width is out of range");
    CHECK_RANGE(height, 0, MAX_BITMAP_HEIGHT, "bitmap height is out of range");
    /* Vše v pořádku - vytvoříme bitmapu */
    bmp = bitmapCreate(width, height);
    if (bmp == NULL)
    {
        LUA_ERROR("bitmapCreate failed");
    }
    LUA_OK
}

/*
 * Funkce volaná ze skriptu
 */
LUA_FUNC clearBitmap(lua_State* L)
{
    /* Kontrola počtu parametrů */
    NUMBER_OF_PARAMETERS(0);
    if (bmp == NULL)
    {
        LUA_ERROR("bitmap does not exist");
    }
    bitmapClear(bmp);
    LUA_OK
}

/*
 * Funkce volaná ze skriptu
 */
LUA_FUNC saveBitmap(lua_State* L)
{
    if (bmp == NULL)
    {
        LUA_ERROR("bitmap does not exist");
    }
    /* Kontrola počtu parametrů */
    NUMBER_OF_PARAMETERS(1);
    /* Kontrola typu parametrů */
    STRINGP(1);
    if (!bitmapSave(bmp, lua_tostring(L, 1)))
        LUA_ERROR("save bitmap to file failed");
    LUA_OK
}

/*
 * Funkce volaná ze skriptu
 */
LUA_FUNC putpixel(lua_State* L)
{
    int i, x, y, r, g, b;
    if (bmp == NULL)
    {
        LUA_ERROR("bitmap does not exist");
    }
    /* Kontrola počtu parametrů */
    NUMBER_OF_PARAMETERS(5);
    /* Kontrola typu parametrů - 5 číselných hodnot */
    for (i=1; i<=5; i++)
    {
        NUMBERP(i);
    }
    /* Kontrola hodnot parametrů */
    x = lua_tointeger(L, 1);
    y = lua_tointeger(L, 2);
    r = lua_tointeger(L, 3);
    g = lua_tointeger(L, 4);
    b = lua_tointeger(L, 5);
    CHECK_RANGE(x, 0, bmp->width-1, "x coordinate is out of range");
    CHECK_RANGE(y, 0, bmp->height-1, "y coordinate is out of range");
#if defined(CHECK_COLOR_COMPONENTS)
    CHECK_RANGE(r, 0, 255, "red color component outside 0-255");
    CHECK_RANGE(g, 0, 255, "green color component outside 0-255");
    CHECK_RANGE(b, 0, 255, "blue color component outside 0-255");
#endif
    bitmapPutPixel(bmp, x, y, (unsigned char)r, (unsigned char)g, (unsigned char)b);
    LUA_OK
}

/*
 * Registrace funkcí dostupných pro programy (skripty) napsané v Lua
 */
void registerLuaFunctions(lua_State* L)
{
    lua_register(L, "createBitmap", createBitmap);
    lua_register(L, "clearBitmap", clearBitmap);
    lua_register(L, "saveBitmap", saveBitmap);
    lua_register(L, "putpixel", putpixel);
}

/*
 * Výpis obsahu zásobníku intepreteru
 */
void printLuaStack(lua_State* L)
{
    int i, max;
    max = lua_gettop(L);
    fprintf(stderr, "Stack items:\n");
    for (i = 1; i <= max; i++)
    {
        fprintf(stderr, "%d/%d\t%s\n", i, max, lua_tostring(L, i));
    }
}

/*
 * Hlavní funkce konzolové aplikace
 */
int main(int argc, char **argv)
{
    int result;
    lua_State* L = lua_open();
    luaL_openlibs(L);
    registerLuaFunctions(L);
    result = luaL_dofile(L, argv[1]);
    /* Zpracování chyby */
    if (result != 0)
    {
        fprintf(stderr, "Error # %d\n", result);
    }
    printLuaStack(L);
    lua_close(L);
    bitmapDestroy(bmp);
    return (result != 0);
}

/*
 * finito
 */ 

2. Testovací skripty využívající rozhraní pro bitmapovou grafiku

Výše popsaná aplikace sice rozšiřuje interpretr jazyka Lua o pouhé čtyři funkce, ovšem pro mnoho úloh počítačové grafiky jsou tyto čtyři funkce dostačující – ve skutečnosti s nimi lze napsat prakticky jakýkoli algoritmus jehož výsledkem je rastrový obrázek, včetně 3D grafiky; jediným problémem je rychlost výpočtu. V této kapitole bude uvedeno několik jednoduchých a krátkých (velikost menší než 1 kB) testovacích skriptů, jejichž výsledkem je buď statický rastrový obrázek, nebo sekvence obrázků, kterou lze za pomoci externích nástrojů (ppmtogif, gifsicle aj.) spojit do animace.

2.1 Vykreslení jednoduchého obrazce založeného na aliasu

-- Vytvoreni jednoducheho obrazce zalozeneho na aliasu

width=256
height=256

createBitmap(width, height)
clearBitmap()

for y=0, height-1 do
    for x=0, width-1 do
        local r=x
        local g=127+127*math.cos(((x-width/2)^2+(y-64)^2)/10)
        local b=y
        putpixel(x, y, r, g, b)
    end
end

saveBitmap("lua8_1.ppm")

-- finito 
lua8_1

2.2 Vykreslení Mandelbrotovy množiny

-- Vykresleni Mandelbrotovy mnoziny

width=320
height=240
maxiter=120

function mandelbrot(x, y, maxiter)
    local zx, zy, cx, cy=0, 0, x, y
    local iter
    for iter=0, maxiter do
        local zx2, zy2 = zx*zx, zy*zy
        -- z=z^2+c
        zx, zy = zx2-zy2+cx, 2*zx*zy+cy
        -- test na bailout
        if zx2+zy2>4 then
            return iter
        end
    end
    return 0
end

createBitmap(width, height)
clearBitmap()

for y=0, height-1 do
    for x=0, width-1 do
        local i=mandelbrot(x/(width/4)-2, y/(height/3)-1.5, maxiter)
        putpixel(x, y, 20*i, 40*i, 60*i)
    end
end

saveBitmap("lua8_2.ppm")

-- finito 
lua8_2

2.3 Napodobení demo efektu „plasmy“

-- Vykresleni plasmy

width=256
height=256

createBitmap(width, height)
clearBitmap()

function putpixel2(x, y, c)
    putpixel(x, y, c, c, c)
end

function plasma(x1, y1, x2, y2, c1, c2, c3, c4)
    local xc, yc = (x1+x2)/2, (y1+y2)/2
    -- podminka pro rekurzivni deleni
    if x2-x1<1 then
        return
    end
    -- 1---12--2
    -- |   |   |
    -- 13--cc--24
    -- |   |   |
    -- 3---34--4
    -- barvy ve stredech stran ctverce
    local c12, c13, c24, c34 = (c1+c2)/2, (c1+c3)/2, (c2+c4)/2, (c3+c4)/2
    -- posun prostredniho bodu
    local cc=(c12+c34)/2+math.random(x2-x1)*2-(x2-x1)
    cc=math.min(cc, 255)
    cc=math.max(cc, 0)
    putpixel2(x1, y1, c1)
    putpixel2(xc, y1, c12)
    putpixel2(x1, yc, c13)
    putpixel2(xc, yc, cc)
    -- rekurzivni rozdeleni ctverce
    plasma(x1, y1, xc, yc, c1, c12, c13, cc)
    plasma(xc, y1, x2, yc, c12, c2, cc, c24)
    plasma(x1, yc, xc, y2, c13, cc, c3, c34)
    plasma(xc, yc, x2, y2, cc, c24, c34, c4)
end

math.randomseed(42)
plasma(0, 0, width-1, height-1, 127, 0, 127, 240)

saveBitmap("lua8_3.ppm")

-- finito 
lua8_3

2.4 RGB spirála

-- Vytvoreni obrazce se spiralou

width=256
height=256

createBitmap(width, height)
clearBitmap()

for y=0, 255 do
    for x=0, 255 do
        local xc=x-width/2
        local yc=y-height/2
        local angle=math.atan2(xc, yc)
        local magnitude=math.sqrt(xc^2 + yc^2)
        local r=127+127*math.cos(20*angle)
        local g=127+127*math.cos(10*angle+1/4*magnitude)
        local b=127+127*math.cos(00*angle+1/6*magnitude)
        putpixel(x, y, r, g, b)
    end
end

saveBitmap("lua8_4.ppm")

-- finito 
lua8_4

2.5 Animace – amplitudová modulace (AM)

-- Animace amplitudove modulace pri postupne
-- zmene frekvence druheho signalu

width=400
height=300

createBitmap(width, height)

-- amplituda a frekvence prvniho signalu
a1=70
f1=4

-- amplituda a menici se frekvence druheho signalu
a2=30

for f2=10, 50 do
    clearBitmap()
    for x=0, width-1, 0.1 do
        wave1 = a1*math.cos(f1*x/(width/2))
        y=height/3 + wave1
        putpixel(x, y, 255, 0, 0)

        wave2 = a2*math.cos(f2*x/(width/2))
        y=height/3 + wave2
        putpixel(x, y, 0, 0, 255)

        -- vypocet AM
        y=3*height/4 + wave1*wave2/50
        putpixel(x, y, 255, 255, 255)
    end

    saveBitmap("lua8_5_"..f2..".ppm")
end

-- finito 
lua8_5

3. Objektově orientované programování v jazyku Lua

V předchozích částech tohoto seriálu jsme si popsali velkou část důležitých vlastností programovacího jazyka Lua. V dnešní části a v části navazující si ukážeme, jakým způsobem lze používat metatabulky a metametody při psaní skriptů využívajících objektově orientovaný přístup. Již v pátém dílu jsme si řekli, že Lua sice nenabízí pro deklaraci tříd a objektů vlastní syntaxi, ale to neznamená, že by objektově orientované programování nebylo možné – objekty lze vytvářet buď na základě uzávěrů (closures) při jejichž použití jsou atributy i metody objektu „zabaleny“ právě v uzávěru (ostatně stejný princip je využitý i v některých funkcionálních jazycích), a/nebo lze využít druhého způsobu založeného na asociativních polích a již zmíněných metatabulkách a metametodách. Tvorba objektů je pak ze sémantického hlediska podobná technice používané v JavaScriptu, který byl inspirovaný jazykem Self a takzvaným prototypováním. Právě těmito technikami se budeme zabývat v následujících kapitolách.

4. Vlastnosti asociativních polí

Mezi podporované datové typy programovacího jazyka Lua patří i asociativní pole, někdy také nazývané hashmapa (hešmapa) či hešovací mapa. Jedná se, jak jsme si již řekli ve třetí části seriálu, o datovou strukturu, v níž jsou uloženy dvojice klíč–hodnota, přičemž klíčem může být hodnota libovolného datového typu kromě typu nil a hodnota může být zcela libovolná (může se jednat i o další asociativní pole, řetězec, funkci atd.). Právě možnost uložení funkce do asociativního pole je důležitá při konstrukci objektů. V případě, že klíče jsou představovány posloupností přirozených čísel, jsou asociativní pole ze sémantického hlediska rovnocenná klasicky chápaným indexovaným polím. Jazyk Lua dokonce umožňuje, aby se při vytváření (konstrukci) asociativního pole klíče vynechaly – v tomto případě překladač automaticky potřebné klíče (indexy) doplní tak, že první hodnotě přiřadí klíč 1, druhé hodnotě 2 atd. (dojde tedy k vytvoření pole, jehož indexy jsou přirozená čísla). Následují ukázky vytvoření (konstrukce) různých asociativních polí:

-- konstrukce asociativního pole se třemi položkami
poleA={klic1="hodnota1", klic2="hodnota2", klic3="hodnota3"}

-- při vynechání klíčů se automaticky doplní hodnoty 1, 2 a 3
poleB={"hodnota1", "hodnota2", "hodnota3"}

-- promíchání obou předešlých způsobů ("hodnota2" má přiřazený klíč 1 a "hodnota4" klíč 2)
poleC={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

-- přepis hodnot některých položek ("hodnota1" na "hodnotax") asociativního pole
-- ve chvíli, kdy tyto položky mají stejné klíče (každý klíč musí být jedinečný)
poleD={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4", klic1="hodnotax"} 

Po zkonstruování asociativního pole je možné přistupovat k jeho jednotlivým prvkům pomocí zápisu, který je prakticky stejný ve většině současných programovacích jazyků: identifikátor_po­le[klíč]. Za identifikátor (jméno) asociativního pole se do hranatých závorek zapíše klíč, což může být libovolná hodnota (typicky číslo či řetězec), proměnná či výraz. V případě, že se v asociativním poli nachází prvek s daným klíčem, je hodnota tohoto prvku vrácena. Pokud prvek naopak nalezen není, vrátí se hodnota nil. Kromě tohoto způsobu zápisu nabízí Lua i alternativní způsob (syntaktický cukr, syntactic sugar), který se často používá v případech, kdy jsou asociativní pole použita ve funkci záznamu (record, struct) či objektu (zde je možné, jak uvidíme dále, použít i variantu s dvojtečkou namísto tečky, přičemž překladač automaticky doplní k deklarované či volané funkci jeden parametr nazvaný self). Tento způsob se zapisuje následovně: identifikátor_po­le.klíč, tj. klíč zde není uveden v hranatých závorkách, ale za identifikátorem pole, od něhož je oddělen tečkou (pokud se jedná o řetězec, není uzavřen v uvozovkách). Příklad použití:

pole={klic1="hodnota1", "hodnota2", klic2="hodnota3", "hodnota4"}

print(pole["klic1"])
print(pole["klic2"])
print(pole["klic3"]) -- neexistující prvek, vypíše se "nil"
print(pole.klic1)
print(pole.klic2)
print(pole.klic3)    -- neexistující prvek, vypíše se "nil" 

5. Vytvoření objektu pomocí uzávěru

Objekty s atributy (datovými složkami) i metodami, které s těmito atributy pracují, je možné poměrně jednoduchým způsobem vytvořit pomocí uzávěru. Funkce, která uzávěr vytváří, se v tomto případě chová jako konstruktor, který objekt explicitně vytvoří (jedná se o lokální proměnnou funkce) a následně vrátí volajícímu programu. Nejjednodušším způsobem, jakým je možné objekt reprezentovat, je asociativní pole popsané v předchozí kapitole. Do tohoto pole se uloží jak všechny atributy objektu, tak i funkce (vystupující v roli metod). Tento – řekněme funkcionální – přístup je ukázán na následujícím demonstračním příkladu, ve kterém je pomocí funkce/konstruktoru Complex vytvořen objekt reprezentující komplexní číslo. Tato funkce vrací lokální asociativní pole nazvané příhodně self, ve kterém jsou vytvořeny atributy real a imag i funkce/metody print a add. Každé volání funkce Complex vrací nové asociativní pole, tj. nový objekt, což je chování, které od konstruktoru většinou požadujeme (pokud bychom naopak potřebovali vytvořit jedináčka, postačuje odstranit klíčové slovo local). Povšimněte si, že v tomto případě nedošlo ke skrytí atributů před okolním programem (privátní atributy resp. metody):

-- Vytvoreni objektu pomoci uzaveru

-- Konstruktor objektu
function Complex(paramReal, paramImag)
    -- asociativni pole, ve kterem jsou ulozeny
    -- jak atributy objektu, tak i jeho metody
    local self={}

    -- vytvoreni a inicializace atributu
    self.real = paramReal
    self.imag = paramImag

    -- vytvoreni metody print
    self.print = function()
        print(self.real.."+i"..self.imag)
    end

    -- vytvoreni metody add
    self.add = function(paramReal, paramImag)
        self.real = self.real + paramReal
        self.imag = self.imag + paramImag
    end

    -- navratovou hodnotou konstruktoru je uzaver
    return self
end

-- vytvoreni dvojice objektu
c1 = Complex(1, 2)
c2 = Complex(3, 4)

-- tisk hodnot obou objektu
c1.print()
c2.print()

-- zmena atributu prvniho objektu
c1.add(10, 20)

-- tisk hodnot obou objektu
c1.print()
c2.print()

-- finito 

6. Deklarace metod vně konstruktoru

V předchozím programu byl celý objekt (= uzávěr) zkonstruován přímo ve funkci Complex. V některých případech, zejména při vytváření objektů s větším množstvím metod, nemusí být tento způsob příliš přehledný. Je však možné použít i jiný způsob, při němž jsou použity externí funkce (uložené v asociativním poli nazvaném Complex), které jako svůj první parametr akceptují asociativní pole s atributy, s nimiž má funkce pracovat. Pole s atributy je vytvořeno funkcí/konstruk­torem Complex.new. Jednou z nevýhod tohoto přístupu je nutnost explicitního uvádění parametru self při vytváření funkcí/metod (v následující kapitole si ukážeme, jak je možné – opět pomocí syntaktického cukru, nikoli nové jazykové konstrukce – zařídit implicitní předávání tohoto parametru. Taktéž způsob volání jednotlivých metod je poněkud neobvyklý, protože se používá zápis Complex.print(ob­jekt) namísto obvyklejšího a kratšího objekt.print(). I tento zápis lze samozřejmě v jazyku Lua použít, je ovšem nutné manipulovat s metatabulkou objektu, což je technika, kterou si vysvětlíme příště.

-- Deklarace metod vne konstruktoru

-- Asociativni pole obsahujici metody
Complex={}

-- Konstruktor (ve skutecnosti jen vhodne
-- pojmenovana funkce)
function Complex.new(paramReal, paramImag)
    local self={}
    self.real = paramReal
    self.imag = paramImag
    return self
end

-- Metoda print s explicitnim predanim parametru self
function Complex.print(self)
    print(self.real.."+i"..self.imag)
end

-- Metoda add s explicitnim predanim parametru self
function Complex.add(self, paramReal, paramImag)
    self.real = self.real + paramReal
    self.imag = self.imag + paramImag
end

-- vytvoreni dvojice objektu
c = Complex.new(1, 2)
c2 = Complex.new(3, 4)

-- tisk hodnot obou objektu
Complex.print(c)
Complex.print(c2)

-- zmena atributu prvniho objektu
Complex.add(c, 10, 20)

-- tisk hodnot obou objektu
Complex.print(c)
Complex.print(c2)

-- finito 

7. Alternativní způsob zápisu metod

V následujícím příkladu je ukázáno, jakým způsobem je možné deklarovat metody, do nichž se parametr nazvaný self předává implicitně, tj. není v hlavičce metody použitý. V případě, že se místo zápisu function A.b použije zápis function A:b, tj. tečka se přepíše na dvojtečku, doplní překladač automaticky self jako první parametr funkce, tj. tento parametr sice není v hlavičce funkce uveden, ale lze s ním uvnitř funkce normálně pracovat. Jedná se o pouhý syntaktický cukr, který nijak chování funkce nemění – už jsme si ostatně uvedli, že i vlastní zápis A.b je taktéž syntaktickým cukrem ekvivalentním k A[„b“]. Při samotném volání funkcí/metod je však stále nutné použít zápisu Complex.print(ob­jekt), protože prozatím neumíme manipulovat s metatabulkami a metametodami. Až si v následující části seriálu ukážeme i tuto techniku, uvidíme, že i samotné volání metod objektu je možné zjednodušit a zpřehlednit, nehledě na to, že nám tím otevírá cesta k tvorbě hierarchie objektů (tj. k dědičnosti).

-- Alternativni zpusob deklarace a volani metod

-- Asociativni pole obsahujici metody
Complex={}

-- Konstruktor (ve skutecnosti jen vhodne
-- pojmenovana funkce)
function Complex.new(paramReal, paramImag)
    local self={}
    self.real = paramReal
    self.imag = paramImag
    return self
end

-- Metoda print s implicitnim predanim parametru self
function Complex:print()
    print(self.real.."+i"..self.imag)
end

-- Metoda add s implicitnim predanim parametru self
function Complex:add(paramReal, paramImag)
    self.real = self.real + paramReal
    self.imag = self.imag + paramImag
end

-- vytvoreni dvojice objektu
c = Complex.new(1, 2)
c2 = Complex.new(3, 4)

-- tisk hodnot obou objektu
Complex.print(c)
Complex.print(c2)

-- zmena atributu prvniho objektu
Complex.add(c, 10, 20)

-- tisk hodnot obou objektu
Complex.print(c)
Complex.print(c2)

-- finito 

widgety

8. Odkazy na Internetu

  1. EN Wikipedia: Prototype Based Programming,
    http://en.wiki­pedia.org/wiki/Pro­totype_based_pro­gramming
  2. Programming in Lua (first edition)
    http://www.lu­a.org/pil/index­.html
  3. Lua home page
    http://www.lu­a.org/
  4. Lua: vestavitelný minimalista
    /clanky/lua-vestavitelny-minimalista/
  5. Lua
    http://www.li­nuxexpres.cz/pra­xe/lua
  6. CZ Wikipedia: Lua
    http://cs.wiki­pedia.org/wiki/Lua
  7. EN Wikipedia: Lua (programming language)
    http://en.wiki­pedia.org/wiki/Lu­a_(programmin­g_language)
  8. The Lua Programming Language
    http://www.ti­obe.com/index­.php/paperinfo/tpci/Lu­a.html
  9. Lua Programming Gems
    http://www.lu­a.org/gems/
  10. LuaForge
    http://luafor­ge.net/
  11. Forge project tree
    http://luafor­ge.net/softwa­remap/trove_lis­t.php

9. Obsah další části seriálu

V následující části seriálu o programovacím jazyce Lua budou popsány další možnosti tohoto jazyka při psaní skriptů využívajících objektově orientovaný přístup. Ukážeme si například, jakým způsobem lze s využitím metatabulek a metametod vytvořit settery a gettery (tj. metody používané pro nastavování popř. čtení atributů objektů) tak, aby se mohly zapisovat pomocí operátoru přiřazení, podobně jako například v jazyku C#, a nikoli explicitním voláním metody. Taktéž si ukážeme, jak lze přetěžovat operátory i další způsob volání metod, který je syntakticky shodný s voláním metod v jazycích C++ či Java. Ve výše uvedeném příkladu by se tedy namísto volání Complex.print(c1) použila konstrukce c1:print(), ovšem pouze za předpokladu, že je vhodným způsobem předefinováno chování metametody __index.

Našli jste v článku chybu?
Lupa.cz: Proč jsou firemní počítače pomalé?

Proč jsou firemní počítače pomalé?

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB

Vitalia.cz: Muž, který miluje příliš. Ženám neimponuje

Muž, který miluje příliš. Ženám neimponuje

Vitalia.cz: Vím, co se učíš, ale netuším, co piješ

Vím, co se učíš, ale netuším, co piješ

Podnikatel.cz: Jak otestovat e-shop. Víte?

Jak otestovat e-shop. Víte?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz

Lupa.cz: Blíží se konec Wi-Fi sítí bez hesla?

Blíží se konec Wi-Fi sítí bez hesla?

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

DigiZone.cz: DVB-T2 ověřeno: seznam TV zveřejněn

DVB-T2 ověřeno: seznam TV zveřejněn

120na80.cz: Galerie: Čínští policisté testují českou minerálku

Galerie: Čínští policisté testují českou minerálku

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Podnikatel.cz: Nemá dluhy? Zjistíte to na poště

Nemá dluhy? Zjistíte to na poště

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Podnikatel.cz: Tyto pojmy k #EET byste měli znát

Tyto pojmy k #EET byste měli znát

Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin