WEBES
ALKALMAZÁSFEJLESZTÉS 1.
Horváth Győző
Egyetemi adjunktus
1117 Budapest,
Pázmány Péter sétány 1/C, 2.420
Tel: (1) 372-2500/1816
Programozási minták2
Programozási minták3
Jól bevált, újrahasznosítható, hatékony
programozási technika
Egyszerű és bonyolult is van közöttük
Gyakorlati minták, sokféle esetben felhasználhatjuk
Tipikus hibák elkerülése, időspórolás, szemléletmód
Előre is tervezünk
Chaining – láncolás4
A legtöbb metódus, ha nem getter, akkor semmivel
sem tér vissza (undefined).
Helyette adjuk vissza az objektumunkat, és így
egymás után hívhatjuk meg a függvényeinket
return this;
Chaining5
var macska = {nev: null,korom: true,setNev: function(nev) {
this.nev = nev;return this;
},dorombol: function() {
console.log( this.nev + ' Purr puurrr!');return this;
},torZuz: function(koromLetor) {
console.log( this.nev + ' tör s zúz!');this.korom = !koromLetor;return this;
},printAllapot: function() {
if (this.korom === false) {console.log( this.nev +
' fájlalja a letört körmét :(' );} else {
console.log( this.nev + ' boldog!' );}return this;
}};
var sc = macska.setNev("Simon's Cat").dorombol() .printAllapot() .torZuz(true) .printAllapot();
// "Simon's Cat Purr puurrr!" // "Simon's Cat boldog!"// "Simon's Cat tör s zúz!"// "Simon's Cat fájlalja a letört körmét :("
Curry6
Függvény-alkotóhoz hasonló minta
Univerzálisabb, letisztultabb formája
Lényeg: olyan függvények létrehozását végzi el,
amelyek korábbi függvények meghívása, néhány
paraméterükkel automatikusan kitöltve.
Működése: closure-ben tárolja el a hívandó
függvényt és paramétereit. Híváskor az eltárolt
paraméterek mellé teszi az aktuálisakat.
Curry7
// Az eltárolandó függvény, szimpla összeadásvar add = function(a, b) {
return a + b;}
// Shorthand függvények legyártásavar inc = curry(add, 1),
add2 = curry(add, 2);
console.log(inc(6)); // 7console.log(add2(6)); // 8
var curry = function (func) {var args = Array.prototype.slice.apply(arguments, [1]);
return function () {return func.apply(null,
args.concat(Array.prototype.slice.apply(arguments)));};
}
Curry8
// Az eltárolandó függvény, mely két szelektort vár, harmadik paraméterként pedig egy színkódot, amire a kijelölt elemek hátterét színezni fogjavar elemeketSzinez = function(hol, milyenElemeket, milyenSzinre) {
return $(hol).find(milyenElemeket).css('backgroundColor', milyenSzinre);}elemeketSzinez('#adatok', '.adat', 'yellow');
// Ha ezzel a két szelektorral rengetegszer hívjuk meg a függvényt, érdemes eltárolni az első két paramétert és egy másik függvényt készíteni, melynek csak a színeket kell megadni:var adatokatSzinez = function(milyenSzinre) {
return elemeketSzinez('#adatok', '.adat', milyenSzinre);}
// Ám ahelyett, hogy minden hasonló esetben írnánk egy ilyen egyedi Shorthandfüggvényt, egyszerűen használhatjuk a curryt:var adatokatSzinez = curry(elemeketSzinez, '#adatok', '.adat');adatokatSzinez('yellow');
Alaptípusok bővítése9
Mivel a sztringet a String() konstruktorfüggvény
hozza létre, ezért minden string prototípusa a
String.prototype. Ha ezt bővítjük, minden sztring
objektumon elérhető lesz a metódus.
if (typeof String.prototype.trim !== 'function') {String.prototype.trim = function() {
return this.replace(/^\s*(\S+)\s*$/, "$1");}
}
' I wanna whitespace! :( '.trim();// => "I wanna whitespace! :("
Alapértelmezett értékek10
Bővítéssel
var defaultValues = {a: 1,b: {c: 2
}}; var o1 = extendDeep({}, defaultValues);
var o2 = extendDeep({}, defaultValues);o2.b.c = 42;
o1.a === 1o1.b.c === 2o1.b.c !== o2.b.c
Alapértelmezett értékek11
Prototípus-objektummal
var defaultValues = {a: 1,b: {c: 2
}}; var o1 = Object.create(defaultValues);
var o2 = Object.create(defaultValues);o1.a === 1
o2.b.c = 42;o1.b.c === 42
o1.b = {c: 100
}o2.b.c === 42
Programtervezési minták12
Programtervezési minták13
A programozási minták szűkebb csoportja
Tipikus objektumorientált probléma megoldása
Elvileg nyelvfüggetlen minták
Főleg OOP-s szemmel vizsgálták ezeket
JavaScriptben, a nyelv dinamikus volta miatt, néha
meglepően egyszerű megoldások születnek
Flyweight minta14
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.
Flyweight15
//A prototípusvar moleculeProto = {
velocity: 10,position: {
x: 100,y: 100
},setPosition: function setPosition(x, y) {
this.position = {x: x,y: y
};}
};//A konkrét molekulákvar m1 = Object.create(moleculeProto);var m2 = Object.create(moleculeProto);
//Változások m1-benm1.velocity = 5;m1.setPosition(90, 102);
//Tesztm2.velocity === 10m1.position.x === 90m2.position.x === 100
Prototype minta16
A klasszikus OOP világában Prototype mintának
nevezik azt, amikor egy objektumot egy meglévő
sablon alapján hozunk létre.
JavaScript
bővítés
prototípus-objektum
Singleton (Egyke)17
Cél: egy osztálynak csak egy példánya legyen
JavaScriptben: egy objektum mindig csak egy
példányban létezik
Modul mintával létrehozott objektumokat is
singletonnal nevezik
var obj = {tul: 'valami';
};
Singleton18
var childSingleton = (function () {var instance,createInstance = function createInstance() {//Privát adattagok és metódusok//...
return {name: 'Sári',dateOfBirth: { /*...*/ },getName: function getName() { /*...*/ },setName: function setName(name) { /*...*/ }
};};
return {get: function () {if (!instance) {instance = createInstance();
}return instance;
}};
})();
var c1 = childSingleton.get();var c2 = childSingleton.get();
c1 === c2
Factory (Gyár)19
Cél
objektumok létrehozása
Ismétlődő tevékenységek hasonló objektumok
létrehozásakor
futásidejű létrehozás
JavaScript
Ld. objektumgyárak
Factory20
Példa: galéria, ami különböző objektumokat jelenít
meg egy felugró dobozban
galeriaGyar('kep').megjelenit('http://www.www.com/majomAFan.jpg');galeriaGyar('video').megjelenit('http://www.youtube.com/notexists.flv');galeriaGyar('url').megjelenit('http://www.www.com');
Factory21
// Absztrakt, tehát nem teljes objektum. Csak a tőle származó objektumok "példányosíthatóak", ha azokban kidolgozásra kerültek az itt nem implementált metódusok.var galeria = function() {
return {doboztKirajzol: function() {
/* Kirajzol egy dobozt */},megjelenit: function() {
console.log('Absztrakt!');}
};};
// A kepGaleria, videoGaleria és wwwGaleria a galeria-tól származó objektumok (funkcionális származtatás). Bennük csak az őstől különböző függvényeket kell implementálni, jelen esetben a megjelenítést - ez mindegyik esetben más és más lesz.var kepGaleria = function() {
var that = galeria();that.megjelenit = function(imgUrl) {
that.doboztKirajzol();/* Megjeleníti a képet */
};return that;
};var videoGaleria = function() {
var that = galeria();that.megjelenit = function(videoUrl) {
that.doboztKirajzol();/* Megjeleníti a videót */
};return that;
};var wwwGaleria = function() {
var that = galeria();that.megjelenit = function(url) {
that.doboztKirajzol();/* Megjeleníti a weboldalt */
};return that;
};
Factory22
// A Factory minta gyártó-függvénye, interfészt biztosít a különböző galériák használatáhozvar galeriaGyar = function(mit) {
switch (mit) {case 'kep':
return kepGaleria();case 'video':
return videoGaleria();case 'url':
return wwwGaleria();}
};
Mediator (Közvetítő)23
Alkalmazás = sok objektum
Szoros kapcsolat nehezebb változtatni
Mediator lazább kapcsolatok
nem közvetlenül egymással kommunikálnak
közvetítő objektumon keresztül
Ld. pl. delegálás
Mediator24
Mediator25
var scoreboard = {// HTML element to be updatedelement: document.getElementById('results'),// update the score displayupdate: function(score) {var i, msg = '';for (i in score) {if (score.hasOwnProperty(i)) {msg += '<p><strong>' + i + '<\/strong>: ';msg += score[i];msg += '<\/p>';
}}this.element.innerHTML = msg;
}};
function Player(name) {this.points = 0;this.name = name;
}Player.prototype.play = function() {
this.points += 1;mediator.played();
};
Mediator26
var mediator = {// all the playersplayers: {},// initializationsetup: function() {
var players = this.players;players.home = new Player('Home');players.guest = new Player('Guest');
},// someone plays, update the scoreplayed: function() {
var players = this.players,score = {
Home: players.home.points,Guest: players.guest.points
};scoreboard.update(score);
},// handle user interactionskeypress: function(e) {
e = e || window.event; // IEif (e.which === 49) { // key "1"
mediator.players.home.play();return;
}if (e.which === 48) { // key "0"
mediator.players.guest.play();return;
}}
};
// go!mediator.setup();window.onkeypress = mediator.keypress;// game over in 30 secondssetTimeout(function() {
window.onkeypress = null;alert('Game over!');
}, 30000);
Observer (megfigyelő)27
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
feliratkozás
leiratkozás
értesítés
Observer28
Ld. eseménykezelés a böngészőben
Elnevezések
megfigyelő/előfizető/feliratkozó
(observer/subscriber/listener)
megfigyelt/tárgyobjektum/kiadó/értesítő/eseménykül
dő (observable/subject/publisher/notifier/event
emitter)
observable.addEventListener('esemény', observer, false);
29
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);
}}
};
30
//observablevar weatherService = extendDeep({
state: 'sunny',changeState: function (newState, hours) {this.state = newState;this.notify(newState, hours);
}}, observable);
//observervar 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 weatherweatherService.changeState('rainy', 3);weatherService.changeState('rainy', 2);weatherService.changeState('rainy', 1);weatherService.changeState('sunny', 1);
Pubsub (Központi eseményvezérlés)31
Observer
szoros kapcsolat
Mediator
lazább kapcsolat
pubsub
mediator megfigyelése
var pubsub = extendDeep({}, observable);
Pubsub32 //observable
var weatherService = {state: 'sunny',changeState: function (newState, hours) {this.state = newState;pubsub.notify(newState, hours);
}};
//observervar smartPhone = {warnForRain: /*...*/,prepareForSun: /*...*/
};//registering at observablepubsub.addObserver('rainy', smartPhone.warnForRain);pubsub.addObserver('sunny', smartPhone.prepareForSun);
//notify observers about changes in the weatherweatherService.changeState('rainy', 3);weatherService.changeState('rainy', 2);weatherService.changeState('rainy', 1);weatherService.changeState('sunny', 1);
Pubsub példa33
<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>
34
// Pre-compile template and "cache" it using closurevar resultTemplate = _.template($( "#resultTemplate" ).html());
// Subscribe to the new search tags topic$.subscribe( "/search/tags" , function( e, tags ) {
$( "#lastQuery" ).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("/search/tags", function( e, tags ) {
$.getJSON( "url" ,{tags: tags,tagmode: "any",format: "json"
},
function( data ){if( !data.items.length ) {
return;}$.publish( "/search/resultSet" , {
items: data.items } );}
);});
Minták a gyakorlatban – példák35
36
var jQuery = (function() {
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
},
...
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
init: function( selector, context, rootjQuery ) { ... },
length: 0,
each: function( callback, args ) { ... },
eq: function( i ) { ... },
end: function() { ... },
push: push,
sort: [].sort,
splice: [].splice
};
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() { ... }; //deep copy
jQuery.extend({
parseJSON: function( data ) { ... },
each: function( object, callback, args ) { ... },
trim: ...,
});
return jQuery;
})();
jQuery37
jQuery()
prototype
…
jQuery.fn=jQuery.prototype
size
init()
length
each()
eq()
selector
constructor
jQ obj = $(‘.adat’) =
new jQuery.fn.init()
context
length
__proto__
[ ]
selector
init()
prototype
jQuery plugin készítés38
Privát névtér létrehozása önkioldó függvénnyel
Biztosítsuk, hogy nem ütközik a $ függvény más
könyvtárak $ jelével
(function( $ ){// Plugin deklarálása
})( jQuery );
jQuery plugin készítés39
Plugin helye: $.fn névtérbe
(function( $ ){$.fn.myPlugin = function() {
// Plugin kódja};
})( jQuery );
jQuery plugin készítés40
this a jQuery objektum, nem kell $ jel
jQuery metóduson belül DOM elem
(function( $ ){
$.fn.myPlugin = function() {// this === $('#element')
this.fadeIn('normal', function(){// this === DOM elem
});};
})( jQuery );
$('#element').myPlugin();
jQuery plugin készítés41
getter
(function( $ ){
$.fn.maxHeight = function() {var max = 0;this.each(function() {
max = Math.max( max, $(this).height() );});return max;
};
})( jQuery );
var tallest = $('div').maxHeight();// A legmagasabb div magasságával tér vissza
jQuery plugin készítés42
setter: chaining biztosítása
(function( $ ){
$.fn.myPlugin = function() {// this === $('#element')
this.fadeIn('normal', function(){// this === DOM elem
});
return this;};
})( jQuery );
$('#element').myPlugin().addClass('piros');
jQuery plugin készítés43
Alapértelmezett értékek és opciók ($.extend)
Függvények elegáns paraméterezése
(function( $ ){$.fn.tooltip = function( options ) {
var settings = {'location' : 'top','background-color' : 'blue'
};return this.each(function() {
if ( options ) { $.extend( settings, options );
}// Tooltip plugin code here//...
});};
})( jQuery );
jQuery plugin készítés44
Csak egy metódussal bővítse az $.fn névteret
események kötése bind-dal, névterezve