Vissza az előzőleg látogatott oldalra (nem elérhető funkció)Vissza a tananyag kezdőlapjára (P)Ugrás a tananyag előző oldalára (E)Ugrás a tananyag következő oldalára (V)Fogalom megjelenítés (nem elérhető funkció)Fogalmak listája (nem elérhető funkció)Oldal nyomtatása (nem elérhető funkció)Oldaltérkép megtekintése (D)Keresés az oldalon (nem elérhető funkció)Súgó megtekintése (S)

Modern programozási minták a kliens és szerveroldali webprogramozásban / Programozási minták JavaScriptben

Tanulási útmutató

Összefoglalás

Ez a fejezet a nyelv fontosabb programozási mintáiba nyújt betekintést.

Programozási minták

Alapértelmezett értékek

Ebben a programozási mintában a cél az, hogy a modellobjektum alapján létrehozott objektumok tulajdonságai meghatározott alapértelmezett értékkel rendelkezzenek.

Forráskód
var defaultValues = {
    a: 1,
    b: {
        c: 2
    }
};

Könnyen belátható, hogy mindkét prototípusos mintával, a bővítéssel és prototípus-objektummal is elérhető a kívánt cél. A bővítés hátránya, hogy a létrejövő objektumokban akkor is megjelenik az adott adattag, ha történetesen egyáltalán nem változik az értéke. Előnye ugyanakkor, hogy az összetett objektumokról is teljes másolat készül, így az állapotok véletlenül sem közösek.

Forráskód
var o1 = extendDeep(, defaultValues);
var o2 = extendDeep(, defaultValues);
o2.b.c = 42;
ok( o1.a === 1, 'Alapértelmezett érték');
ok( o1.b.c === 2, 'Belső objektumbeli érték is elérhető');
ok( o1.b.c !== o2.b.c, 'Referenciatípusoknál nincs interferencia');

A bővítésnél sokkal memória-hatékonyabb megoldást nyújt a prototípusos kapcsolat. Ekkor a modellobjektumot a létrejövő objektumok prototípusának vesszük fel. Mindaddig, amíg a célobjektum felül nem definiálja saját magán belül az adott tulajdonságot, addig azok a prototípusban elérhetők. Így ezek úgy viselkednek, mint alapértelmezett értékek. Ennek a megközelítésnek egyetlen veszélye a prototípus-objektumban definiált referenciatípusokban rejlik, ugyanis ezek célobjektumban történő felüldefiniálásukhoz teljes másolatuk szükséges.

Forráskód
var o1 = Object.create(defaultValues);
var o2 = Object.create(defaultValues);
ok( o1.a === 1, 'Alapértelmezett értékek elérhetők');
o2.b.c = 42;
ok( o1.b.c === 42, 'Referenciatípusokkal vigyázni kell!');
o1.b = {
    c: 100
}
ok( o2.b.c === 42, 'Referenciatípusoknál teljes felüldefiniálásuk szükséges');

A fenti példabeli defaultValues objektumbeli tulajdonságok tekinthetők az o1 és o2 objektum alapértelmezett értékeinek. Ezek az értékek aztán később az o1 és o2 objektumokon belül felüldefiniálhatók.

Vissza a tartalomjegyzékhez

Flyweight minta

A Flyweight minta a rendszer erőforrásaival gazdálkodik oly módon, hogy egy külön objektumban tárolja az újrahasznosítható tulajdonságokat és metódusokat ahelyett, hogy minden célobjektumba ezek külön bemásolásra kerülnének. Ez akkor tud sok memóriát megtakarítani, ha sok ugyanolyan objektum van. Míg a klasszikus OOP világában ennek megvalósítása viszonylag komplex feladat, addig JavaScriptben a prototípus-objektum éppen erre a feladatra szolgál.

Képzeljük el, hogy egy szimulációban rengeteg molekula mozgását valósítjuk meg. Egy molekuláról viszonylag sok információt tárolunk, és sok metódus lehet felelős a működéséért. Ahelyett, hogy ezeket mind lemásolnánk, létrehozunk egy molekula prototípust az alapértelmezett értékekkel és metódusokkal, és a konkrét molekulaobjektumokat ez alapján hozzuk létre. Ha egy molekula valamilyen paramétere, iránya vagy sebessége megváltozik, akkor azt a saját objektumában felülírja.

Forráskód
//A prototípus
var moleculeProto = {
    velocity: 10,
    position: {
        x: 100,
        y: 100
    },
    setPosition: function setPosition(x, y) {
        this.position = {
            x: x,
            y: y
        };
    }
};
//A konkrét molekulák
var m1 = Object.create(moleculeProto);
var m2 = Object.create(moleculeProto);
//Változások m1-ben
m1.velocity = 5;
m1.setPosition(90, 102);
//Teszt
ok( m2.velocity === 10, 'm1 sebességváltozása nem érinti m2 alapértelmezett sebességét');
ok( m1.position.x === 90, 'm1 x pozíciója megváltozott');
ok( m2.position.x === 100, 'A prototípus pozíciója nem változott');

Mivel a prototípusban a position objektum referenciatípus, így annak tagját közvetlenül módosítva minden gyerekobjektumban a változás érvényesülne. Annak érdekében, hogy a változás csak az adott gyerekobjektumon következzen be, vezettük be a setPosition() metódust, mely a gyerekobjektumban hoz létre egy új position objektumot.

Vissza a tartalomjegyzékhez

Prototype minta

A klasszikus OOP világában Prototype mintának nevezik azt, amikor egy objektumot egy meglévő sablon alapján hozunk létre. JavaScriptben ez a prototípusosság mindkét formájával megvalósítható: a bővítés során a modellobjektum mint prototípus tulajdonságai másolódnak a célobjektumba; a prototípus-objektum beállításával pedig az valósítható meg, hogy egy gyerekobjektum látható interfésze tükrözi a prototípus-objektum tulajdonságait is. (ld. A Prototype minta JavaScriptben)

Vissza a tartalomjegyzékhez

Az Egyke (Singleton) minta

Az Egyke minta a klasszikus OOP-ben arra szolgál, hogy egy osztály csak egy példányban létezzen. JavaScriptben nincsenek osztályok, csak objektumok. Amikor létrehozunk egy objektumot, akkor az egyedi, nincs más hozzá hasonló, tehát megfelel az Egyke mintának. JavaScriptben tehát az objektumliterállal létrehozott objektumok egykék, nincs szükség speciális szintaxisra.

Az egykék annyiban különböznek a statikus osztályoktól, hogy létrehozásukat késleltethetjük. Az objektumliterállal létrehozott objektumok azonnal létrejönnek függetlenül attól, hogy szükségünk van-e rá vagy sem. Ha csak akkor szeretnénk létrehozni őket, ha szükségünk van rá, akkor létrehozásukat egy függvénybe csomagolhatjuk, és closure-ben tárolhatjuk a létrejött objektumot.

Forráskód
var childSingleton = (function () {
    var instance,
        createInstance = function createInstance() {
            //Privát adattagok és metódusok
            //...
            return {
                name: 'Sári',
                dateOfBirth: {
                    year: 2004,
                    month: 11,
                    day: 14
                },
                getName: function getName() {
                    return this.name;
                },
                setName: function setName(name) {
                    this.name = name;
                }
            };
        };
    return {
        get: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
var c1 = childSingleton.get();
var c2 = childSingleton.get();
ok( c1 === c2, 'A két objektum ugyanaz');

Vissza a tartalomjegyzékhez

A közvetítő minta (Mediator)

Egy alkalmazás általában sok egymással kommunikáló objektumokból áll. Ezeket az objektumokat úgy kell szervezni, hogy az alkalmazás tesztelhetősége és karbantarthatósága ne sérüljön. Ahogy az alkalmazás nő, az objektumokat újra és újra át kell szervezni. Ha az objektumok közvetlen kapcsolatban vannak egymással, akkor nem egyszerű úgy megváltoztatni egy objektumot, hogy az ne befolyásolna más objektumokat.

A közvetítő minta abban segít, hogy a szoros kapcsolatok helyett lazábban összekapcsolt objektumokat hozzunk létre, és ezzel növeljük alkalmazásunk karbantarthatóságát. Ezt úgy éri el, hogy az egyes objektumok nem közvetlenül egymással kommunikálnak, hanem egy központi objektumon, a közvetítőn keresztül. Amikor az egyik objektum állapota megváltozik, akkor értesíti a közvetítőt, az pedig továbbítja az információt az abban érdekelt többi objektum felé.

A közvetítő minta sematikus ábrájaA közvetítő minta sematikus ábrája

Böngészők esetében a közvetítő minta valósul meg események delegálása esetén. Ekkor egy központi objektumhoz, például a dokumentum legfelső szintjén lévő document elemhez rendeljük az eseménykezelő függvényeket, majd azokban döntjük el, hogy az esemény körülményeitől függően milyen logikát futtatunk. Ekkor a document tölti be a mediátor szerepét.

Forráskód
$(document)
    .on('click', 'div', function (e) {
        //A kattintás div elemen történt
    })
    .on('click', 'a', function (e) {
        //A kattintás a elemen történt
    });

Vissza a tartalomjegyzékhez

A megfigyelő minta (Observer)

A megfigyelő minta sematikus ábrájaA megfigyelő minta sematikus ábrája

A megfigyelő mintában egy vagy több objektum (a megfigyelők) fejezi ki érdeklődését egy másik objektum (a megfigyelt) állapotváltozásai iránt, valamilyen módon kapcsolódva a megfigyelt objektumhoz. A megfigyelt objektum egy listában tárolja az iránta érdeklődő objektumokat, és állapotváltozásakor értesíti őket. A megfigyelő objektumok bármikor lecsatlakozhatnak a megfigyelt objektumról.

A megfigyelő minta szolgál az eseménykezelés megvalósítására, és így a legtöbb felhasználói felület valamilyen módon implementálja. A böngészők esetében sincs ez másképp. Amikor feliratkozunk egy DOM objektum eseményére egy eseménykezelő függvénnyel, akkor tulajdonképpen kifejezzük érdeklődésünket az adott elem adott állapotváltozása (eseménye) felé, és megkérjük az elemet, hogy értesítsen a változás bekövetkeztekor az eseménykezelő függvény meghívásával. Ebben az esetben az adott DOM objektum a megfigyelt, és a függvény a megfigyelő.

Forráskód
observable.addEventListener('esemény', observer, false);

A klasszikus OOP mintában a megfigyelő minta megvalósítása megfelelő ősosztályok definiálásából vagy interfészek kialakításából áll. A megfigyelt objektum valamilyen módon implementálja az Observable interfészt, a megfigyelők az Observer interfészt. A megfigyelt egy listában tárolja a hozzá feljelentkezetteket, és beszédes metódusokon keresztül teszi lehetővé ezek kezelését:

A változás bekövetkeztekor meghívja a notifyObservers() metódusát, amely meghívja a megfigyelők notify() metódusát, amit azoknak az Observer interfészből implementálni kell.

JavaScriptben annyiban egyszerűbb a helyzet, hogy a függvények első osztályú objektumok, és így közvetlenül megjelenhetnek megfigyelőként, értesítéskor közvetlenül meghívhatók. Ez látható a fenti eseménykezelős példában is, ahol observer egy függvény.

A megfigyelő minta természetesen több, mint a böngészőbeli eseménykezelés. Segítségével egyedi eseményeket hozhatunk létre.

A fő motiváció e minta mögött, hogy az objektumok lazább kapcsolatát hozza létre. Ahelyett, hogy az egyik objektum közvetlenül egy másik objektum metódusát hívja meg, az objektumok be tudják regisztrálni metódusaikat egy adott eseményhez, és ennek bekövetkeztekor automatikusan értesülnek.

A minta szereplőire többféleképpen hivatkozik a szakirodalom:

A minta megvalósításakor a megfigyelt objektumban egy tömböt vesznek fel a feliratkozó függvények tárolására. Mivel egyedi eseményeket is szeretnénk létrehozni, ezért általában az egyes események egy objektumliterál kulcsát képezik, és eseményenként tárolják a feliratkozó függvények tömbjét. Feliratkozáskor csupán e tömbbe kell a feliratkozót helyezni. Leiratkozáskor e tömbből kell a függvényt kivenni, értesítéskor pedig az adott eseményhez tartozó tömbön kell végigmenni, és a függvényeket meghívni. A naiv megoldás az alábbi (többek között nem tartalmaz típusellenőrzést, nem rugalmas a paraméterek elhagyására, stb.)

Forráskód
var observable = {
    observers: ,
    addObserver: function(type, fn) {
        if (typeof this.observers[type] === "undefined") {
            this.observers[type] = [];
        }
        this.observers[type].push(fn);
    },
    removeObserver: function(type, fn) {
        var i,
            observers = this.observers[type];
        for (i = 0; i < observers.length; i += 1) {
            if (observers[i] === fn) {
                observers.splice(i, 1);
            }
        }
    },
    notify: function(type) {
        var i,
            observers = this.observers[type] || [],
            args = [].slice.call(arguments, 1);
        for (i = 0; i < observers.length; i += 1) {
            observers[i].apply(this, args);
        }
    }
};

A fenti műveletcsoportot általában mély kiterjesztéssel tesszük elérhetővé a megfigyelt objektumokon.

Forráskód
//observable
var weatherService = extendDeep(
    {
        state: 'sunny',
        changeState: function (newState, hours) {
            this.state = newState;
            this.notify(newState, hours);
        }
    }, 
    observable);
//observer
var smartPhone = {
    warnForRain: function (hours) {
        console.log('Rain is coming in ', hours, 'hours!');
    },
    prepareForSun: function (hours) {
        console.log('The sun will shine in ', hours, 'hours!');
    }
};
//registering at observable
weatherService.addObserver('rainy', smartPhone.warnForRain);
weatherService.addObserver('sunny', smartPhone.prepareForSun);
//notify observers about changes in the weather
weatherService.changeState('rainy', 3);
weatherService.changeState('rainy', 2);
weatherService.changeState('rainy', 1);
weatherService.changeState('sunny', 1);
//Output
// Rain is coming in 3 hours!
// Rain is coming in 2 hours!
// Rain is coming in 1 hours!
// The sun will shine in 3 hours!

Backbone-ban a Backbone.Events objektum gyakorlatilag a fenti funkcionalitást hordozza, csupán metódusai mások, szándékosan a jQuery szintaktikáját utánozva:

Ezekkel a következőképpen lehet megfigyelhető objektumokat, vagy más néven eseményküldő objektumokat létrehozni.

Forráskód
var weatherService = _.extend(
    {
        state: 'sunny',
        changeState: function (newState, hours) {
            state = newState;
            this.trigger(newState, hours);
        }
    }, 
    Backbone.Events);
//Regisztrálás
weatherService.on('rainy', smartPhone.warnForRain);

Vissza a tartalomjegyzékhez

A központi eseményvezérlő minta (pubsub)

A megfigyelő minta egyik nagy hátránya, hogy ugyan értesítéskor közvetlen metódushívás nincsen a megfigyelt és megfigyelő között, mégis a regisztráláshoz a megfigyelőnek tartalmaznia kell referenciát a megfigyeltre. Ilyen szempontból a két objektum között még mindig szoros a kapcsolat. Az objektumok közötti kapcsolatot a közvetítő minta segítségével lazíthatjuk úgy, hogy minden objektum egy központi eseményvezérlő objektumon keresztül hívja meg eseményeit, és ennek az objektumnak az eseményeire is regisztrál. Ez a minta tehát tulajdonképpen két mintának az együttes használatából született. A központi eseményvezérlő objektum a közvetítő mintát valósítja meg, ami egyben megfigyelhető is, ami a megfigyelő mintának felel meg. A mintát (részben tévesen) szokták pubsub mintának is hívni az angol publish-subscribe szavak alapján, bár ez a megfigyelő mintára is illik.

A konkrét megvalósításban tehát egy központi objektumot kell kijelölnünk és megfigyelhetővé tennünk, és az alkalmazás összes többi objektuma ezen keresztül kommunikál egymással eseményeket küldve, vagy azokra feliratkozva. Ennek a megoldásnak nagy előnye, hogy így az alkalmazás objektumai lazán kapcsoltak lesznek, és így függetlenül is tesztelhetők, hátránya, hogy nehezebben követhető, mikor mi történik.

Forráskód
var pubsub = extendDeep(, observable);
//observable
var weatherService = {
    state: 'sunny',
    changeState: function (newState, hours) {
        this.state = newState;
        pubsub.notify(newState, hours);
    }
};
//observer
var smartPhone = {
    warnForRain: /*...*/,
    prepareForSun: /*...*/
};
//registering at observable
pubsub.addObserver('rainy', smartPhone.warnForRain);
pubsub.addObserver('sunny', smartPhone.prepareForSun);
//notify observers about changes in the weather
weatherService.changeState('rainy', 3);
weatherService.changeState('rainy', 2);
weatherService.changeState('rainy', 1);
weatherService.changeState('sunny', 1);

Vagy ugyanezt a Backbone.Events segítségével:

Forráskód
var pubsub = extendDeep(, Backbone.Events);
//observable
var weatherService = {
    state: 'sunny',
    changeState: function (newState, hours) {
        this.state = newState;
        pubsub.trigger(newState, hours);
    }
};
//registering at observable
pubsub.on('rainy', smartPhone.warnForRain);
pubsub.on('sunny', smartPhone.prepareForSun);

Az eseményeket sokszor névterezik vagy szegmensekre osztják, pl. /new/data vagy change:data. Ezekkel jobban azonosítható az esemény jellege.

Ez a minta közel áll az üzenetküldő sorokhoz, amelyek sokkal alaposabban adminisztrálják az események állapotát.

Példaképpen nézzük meg, hogy hogyan választhatók szét egy jQuery alapú Ajax alkalmazás részei egy központi eseményvezérlő segítségével. (A példa eredetije Addy Osmani tervezési minták könyvének pubsub mintáról szóló fejezetéből való.) A kódban Ben Alman jQuery kiegészítője látható, amely a $ függényhez kapcsolja a megfelelő függvényeket (publish, subscribe, unsubscribe). A lenti kódban látható HTML sablonokról később lesz szó.

A HTML a következőképpen néz ki:

Forráskód
<form id="flickrSearch">
    <input type="text" name="tag" id="query"/>
    <input type="submit" name="submit" value="submit"/>
</form>
<div id="lastQuery"></div>
<div id="searchResults"></div>
<script id="resultTemplate" type="text/html">
    <% _.each(items, function( item ){  %>
            <li><p><img src="<%= item.media.m %>"/></p></li>
    <% });%>
</script>

A JavaScript pedig:

Forráskód
;(function( $ ) {
    // Pre-compile template and "cache" it using closure
    var resultTemplate = _.template($( "#resultTemplate" ).html());
    // Subscribe to the new search tags topic
    $.subscribe( "/search/tags" , function( e, tags ) {
       $( "#searchResults" )
            .html("<p>Searched for:<strong>" + tags + "</strong></p>");
    });
    // Subscribe to the new results topic
    $.subscribe( "/search/resultSet" , function( e, results ){
       $( "#searchResults" ).append(resultTemplate( results ));
    });
    // Submit a search query and publish tags on the /search/tags topic
    $( "#flickrSearch" ).submit( function( e ) {
       e.preventDefault();
       var tags = $(this).find( "#query").val();
       if ( !tags ){
        return;
       }
       $.publish( "/search/tags" , [ $.trim(tags) ]);
    });
    // Subscribe to new tags being published and perform
    // a search query using them. Once data has returned
    // publish this data for the rest of the application
    // to consume
    $.subscribe("/search/tags", function( e, tags ) {
        $.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?" ,{
                tags: tags,
                tagmode: "any",
                format: "json"
            },
            function( data ){
                if( !data.items.length ) {
                    return;
                }
                $.publish( "/search/resultSet" , { items: data.items } );
            }
        );
    });
})( jQuery );

Programozási és tervezési minták JavaScriptben

Flash lejátszó letöltése

Programozási minták

Vissza a tartalomjegyzékhez

Új Széchenyi terv
A projekt az Európai Unió támogatásával, az Európai Szociális Alap társfinanszirozásával valósul meg.

A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.