ugrás a tartalomhoz

JavaScript design patternek

Bártházi András · 2012. Feb. 22. (Sze), 10.17

A design patternek, vagyis magyarul a tervezési minták olyan építőkockák, melyek a gyakran előforduló, tipikus problémákra kínálnak jól működő, egységes, kalapból előhúzható megoldásokat. A feladatok mögött fel lehet fedezni mintázatokat, melyeket végül mindig ugyanúgy lehet a legjobban megoldani - ezen megoldások gyűjtőnevéről beszélünk. A bejegyzésben pár JavaScript tervezési mintáról írok, illetve adok egy pár linket is a témában.

Ha valaki általában kíváncsi a design patternekre, meg hogy honnan jött a fogalom, ajánlom neki ezt a blogbejegyzést, ahol Parragh Szabolcs írt egy rövid, de érdekes összefoglalót a témáról. Szintén jó összefoglalót ad Addy Osmani, a JavaScript tervezési mintákról írt könyvében.

Rátérve a bejegyzésem témájára, két patternt szeretnék kiemelni, melyeket azért tartok fontosnak, mert a kód szervezettségünkön, főként ha nagyobb mennyiségű JavaScript kódot írunk, nagyon-nagyon sokat tudnak segíteni. Az egyik a Singleton, a másik pedig a Modul pattern. A Singleton egy olyan erőforrást jelent, melyből az alkalmazásunkban egy példányt tartunk fenn. A Modul az objektum orientált nyelvek class-ait valósítja meg, privát és publikus metódusokkal, tulajdonságokkal. Rengeteg más pattern van még, érdemes mindegyikben gyakorlatot szerezni, és megérteni, hogy hogyan működnek. Továbbiakra is kitérek még néhány mondat erejéig.

Singleton pattern

Ahogy tehát említettem, a Singleton pattern segítségével egy olyan erőforrást valósíthatunk meg, melyből csak egy példány létezik a kódunkban, szemben az osztályokkal, melyekből több példányt is létrehozhatunk. Ha JavaScriptben létrehozunk egy sima függvényt, vagy változót, abból is csak egy példány lesz, így felmerülthet a kérdés, hogy egy Singleton megvalósítás miben is különbözik ettől, miben nyújt többet. A szervezettség a válasz, egy Singleton több összefüggő, azonos célú függvényt, változót gyűjt össze, és csomagol egybe.

JavaScriptben sokféleképpen megvalósíthatjuk ezt a patternt, de a legegyszerűbb megoldás egy objektum deklarálása, mely felsorolja a metódusokat és a változókat:

  1. var SingletonNeve = {  
  2.   metodus1: function(p1, p2) {  
  3.     return p1+p2;  
  4.   },  
  5.   metodus2: function(p1, p2) {  
  6.     return SingletonNeve.tulajdonsag1 + ' ' + SingletonNeve.metodus1(p1, p2);  
  7.   },  
  8.   tulajdonsag1: "Hello, én vagyok a Singleton!",  
  9.   tulajdonsag2: "Hello Weblabor!"  
  10. }  

A kódból az is látható, hogy egy Singleton hogyan tud magára hivatkozni: a saját nevének megadásával. A Singelon.metodus2(1,3) meghívásával a "Hello, én vagyok a Singleton! 4" választ fogjuk kapni.

Modul pattern

A Modul pattern egy sokkal összetettebben működő minta. A hagyományos OO osztályok mintájára privát és publikus metódusokat, tulajdonságokat tud létrehozni, melyek a JavaScript new kulcsszavával példányosíthatóak. Hatalmas trükkök ugyan nincsenek benne, de azért szerepet kapnak a closure-ök, illetve a new operátor használata:

  1. var ModulNeve = function(p1, p2){  
  2.   // privát metódusok  
  3.   function privat1(n){  
  4.     return "a végső válasz: "+n;  
  5.   }  
  6.   // privát változók  
  7.   var privat_valtozo1;  
  8.   // konstruktor  
  9.   privat_valtozo1 = p2 - p1 + 23;  
  10.   // publikus metódusok, változók  
  11.   return {  
  12.     publikus1: function() {  
  13.       return privat1(this.publikus_valtozo1 + privat_valtozo1);  
  14.     },  
  15.     publikus_valtozo1: 42  
  16.   }  
  17. };  
  18.   
  19. var peldany = new ModulNeve(4, 8);  
  20. peldany.publikus_valtozo1 = 15;  
  21. console.log(peldany.publikus1());  

Nézzük mi történik. Új, new operátorral példányosítható osztályt a JavaScriptben egy sima függvény deklarációval hozhatunk létre, ez lesz a konstruktorunk. A függvény által returnnel visszaadott objektum lesz az az objektum, amit visszaad a new. Az itt deklarált metódusokat, változókat el lehet érni kívülről. A closure-ök szabályai szerint, a konstruktor függvényben deklarált függvények és változók láthatóak a publikus metódusokból, de kívülről nem hozzáférhetőek.

Ez a tervezési minta elég jól átlátható kódot jelent, és alapvetően teljesen jól is működik. Egyik problémája, hogy ha utólag dinamikusan kiegészítjük a modult egy új metódussal, az - értelemszerűen - nem fogja látni a privát metódusokat, változókat.

A modul patternnek vannak további változatai, illetve a komolyabb JavaScript keretrendszerek (pl. YUI, DŌJŌ), vagy célkönyvtárak különféle módokon kényelmi szolgáltatásokat nyújtanak a használatához, ezeknek érdemes utánaolvasni.

További patternek

A többi pattern közül nehezen tudok kiemelni párat, nem azért mert nem lennének fontosak, hanem mert ugyanannyira jól jön a legtöbb a mindennapi munkában. Ami talán fontosabb lehet a többinél, hogy nagyobb kódbázisnál mindenképp érdemes a Namespace patternt használni, sokszor jól jöhet az Observer pattern, de ezek eléggé ad-hoc válogatott példák voltak.

További olvasnivaló

A források talán az Essential JavaScript Design Patterns For Beginners könyv és a JavaScript pattern and antipattern collection oldal, de érdemes még megnézni ezt a Module patternről szóló blogbejegyzést, az O'Reilly által kiadott JavaScript patterns könyvet. Létezik egy ismertebb blog is a témában, mely a http://www.jspatterns.com/ címen érhető el.

 
1

Alternatíva a singleton definíciójára

Hidvégi Gábor · 2012. Feb. 22. (Sze), 12.37
A singletont én a következőképp szoktam használni, így lehetőség van belső változók és függvények létrehozására:
  1. var Singleton = new function Singleton() {  
  2.   var valtozo = 'ertek';  
  3.   
  4.   function privat_fuggveny() {  
  5.   }  
  6.   
  7.   this.publikus_fuggveny = function publikus_fuggveny() {  
  8.     return valtozo;  
  9.   }  
  10. }  
  11.   
  12. var valtozo = Singleton.publikus_fuggveny();  
3

függvény neve

Bártházi András · 2012. Feb. 22. (Sze), 13.38

Van valami oka, hogy a függvénynek is nevet adtál? Anonymous függvényekkel is menne:

  1. var Singleton = new function() {  
  2.   var valtozo = 'ertek';  
  3.   
  4.   function privat_fuggveny() {  
  5.   }  
  6.   
  7.   this.publikus_fuggveny = function() {  
  8.     return valtozo;  
  9.   }  
  10. }  
  11.   
  12. var valtozo = Singleton.publikus_fuggveny();  
4

Hibakeresés

Hidvégi Gábor · 2012. Feb. 22. (Sze), 13.45
A Firebug hiba esetén így kiírja a függvény nevét, és amíg átváltok a szövegszerkesztőre, már tudok gondolkozni a problémán, mert be tudom tájolni, mi okozhatott gondot.
20

én valahol azt olvastam, hogy

Karvaly84 · 2012. Feb. 23. (Cs), 15.52
én valahol azt olvastam, hogy mindig adjunk nevet a függvénynek, és pl. az a argumentumokra úgy hivatkozzunk, hogy fügvényneve.arguments, és a függvénydeklaráció helyett meg alkalmazzunk függvénykifejezést. Azt nem tudom, hogy melyiknek mi a hátránya éppen, vagy az előnye, én keverve használom ahogy éppen rövidebb vagy praktikusabb.
21

Valahol?

Poetro · 2012. Feb. 23. (Cs), 17.02
Valahol-ra nem érdemes adni. A függvényneve.arguments pedig nem helyes megközelítés eleve, mivel már régóta depricated. Függvénykifejezésnek pedig nevet csak akkor érdemes adni, ha a függvényen belül hivatkozni fogsz a nevére.
8

Ez a megoldás egy olyan

Hidvégi Gábor · 2012. Feb. 22. (Sze), 15.23
Ez a megoldás egy olyan előnnyel jár, hogy a szintaktikája miatt nem lehet belőle több példányt létrehozni, ezért nem kell beletenni ilyen ellenőrzést.
2

AMD és CommonJS

oroce · 2012. Feb. 22. (Sze), 12.47
Szerintem érdemes megemlíteni még az AMD és a CommonJS patternt.

Akit érdekel itt talál róla írást: http://addyosmani.com/writing-modular-js/ és http://tagneto.blogspot.com/

Én egyébként require.js-t (AMD pattern) használok Backbonenal (Constructor, Observer patternt).
5

A singleton pattern lényege,

dropout · 2012. Feb. 22. (Sze), 14.37
A singleton pattern lényege, hogy csak egy példány létezhet belőle egy kontextusban mondjuk. Ebben (vagy bármilyen más js implementációban) hogyan valósul ez meg? Mi gátol meg engem attól, hogy új példányt hozzak létre az alábbi módon, mert mondjuk nem értek a dizájn mintákhoz, vagy mondjuk nincs benne a nevében a Singleton és nem tudom, hogy ez az akar lenni.
  1. var masikSingletonNeve = SingletonNeve;  
  2. masikSingletonNeve.metodus1();  
6

Futtasd le

Bártházi András · 2012. Feb. 22. (Sze), 14.43
  1. var Obj = { x: 1, y: 2 };  
  2. var ObjCopy = Obj;  
  3. ObjCopy.x = 3;  
  4. console.log(Obj.x);  
7

Nem új

Poetro · 2012. Feb. 22. (Sze), 14.44
A fenti nem hoz létre új példányt, csak ugyanarra az objektumra mutat. Ha a fentinél valódibb Singleton-ra van szükség JavaScript alatt, akkor arra találhatunk több példát is. Köszönet Leonuh-nak a linkért.
9

Igen mindkettőtök válasza

dropout · 2012. Feb. 22. (Sze), 15.32
Igen mindkettőtök válasza eliminálja a kérdésemet, de itt a következő menet, mert igazából erre gondoltam, csak rosszul írtam le a kérdést:
  1. <!doctype html>  
  2. <html lang="en">  
  3.     <head>  
  4.         <title>JavaScript Patterns</title>  
  5.         <meta charset="utf-8">  
  6.     </head>  
  7.     <body>  
  8.         <script>  
  9.             /* Title: Singleton  
  10.              Description: restricts object creation for a class to only one instance  
  11.              */  
  12.   
  13.             var obj = {  
  14.                 myprop:'my value'  
  15.             };  
  16.   
  17.             var obj2 = {  
  18.                 myprop:'my value'  
  19.             };  
  20.             obj === obj2; // false  
  21.             obj == obj2;  // false  
  22.   
  23.             var uni = new Universe();  
  24.             var uni2 = new Universe();  
  25.             uni === uni2; // true  
  26.   
  27.             function Universe() {  
  28.   
  29.                 // do we have an existing instance?  
  30.                 if (typeof Universe.instance === 'object') {  
  31.                     return Universe.instance;  
  32.                 }  
  33.   
  34.                 // proceed as normal  
  35.                 this.start_time = 0;  
  36.                 this.bang = "Big";  
  37.   
  38.                 // cache  
  39.                 Universe.instance = this;  
  40.   
  41.                 // implicit return:  
  42.                 // return this;  
  43.             }  
  44.   
  45.             // testing  
  46.             var uni = new Universe();  
  47.             var uni2 = new Universe();  
  48.             uni === uni2; // true  
  49.   
  50.             /*** Instance in a Closure ***/  
  51.   
  52.             function Universe() {  
  53.   
  54.                 // the cached instance  
  55.                 var instance = this;  
  56.   
  57.                 // proceed as normal  
  58.                 this.start_time = 0;  
  59.                 this.bang = "Big";  
  60.   
  61.                 // rewrite the contructor  
  62.                 Universe = function () {  
  63.                     return instance;  
  64.                 };  
  65.             }  
  66.   
  67.             function Universe() {  
  68.   
  69.                 // the cached instance  
  70.                 var instance;  
  71.   
  72.                 // rewrite the constructor  
  73.                 Universe = function Universe() {  
  74.                     return instance;  
  75.                 };  
  76.   
  77.                 // carry over the prototype properties  
  78.                 Universe.prototype = this;  
  79.   
  80.                 // the instance  
  81.                 instance = new Universe();  
  82.   
  83.                 // reset the constructor pointer  
  84.                 instance.constructor = Universe;  
  85.   
  86.                 // all the functionality  
  87.                 instance.start_time = 0;  
  88.                 instance.bang = "Big";  
  89.   
  90.                 return instance;  
  91.             }  
  92.   
  93.   
  94.             // testing  
  95.             var uni = new Universe();  
  96.             var uni2 = new Universe();  
  97.             uni === uni2; // true  
  98.   
  99.             // adding to the prototype  
  100.             Universe.prototype.nothing = true;  
  101.   
  102.             var uni = new Universe();  
  103.   
  104.             // again adding to the prototype  
  105.             // after the initial object is created  
  106.             Universe.prototype.everything = true;  
  107.   
  108.             var uni2 = new Universe();  
  109.   
  110.             // only the original prototype was  
  111.             // linked to the objects  
  112.             uni.nothing; // true  
  113.             uni2.nothing; // true  
  114.             uni.everything; // undefined  
  115.             uni2.everything; // undefined  
  116.   
  117.             // that sounds right:  
  118.             uni.constructor.name; // "Universe"  
  119.   
  120.             // but that's odd:  
  121.             uni.constructor === Universe; // false  
  122.   
  123.             var Universe;  
  124.   
  125.             (function () {  
  126.   
  127.                 var instance;  
  128.   
  129.                 Universe = function Universe() {  
  130.   
  131.                     if (instance) {  
  132.                         return instance;  
  133.                     }  
  134.   
  135.                     instance = this;  
  136.   
  137.                     // all the functionality  
  138.                     this.start_time = 0;  
  139.                     this.bang = "Big";  
  140.                 };  
  141.             }());  
  142.   
  143.             // reference  
  144.             // http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript  
  145.             // http://shop.oreilly.com/product/9780596806767.do?sortby=publicationDate  
  146.   
  147.   
  148.             /**  
  149.              * Új cucc a példához: jó hogy a konstruktor le van védve de egy jquery/mootools szerű clone  
  150.              * ellen ez nem véd, és egy egységsugarú user összevissza használhatja a keretrendszer függvényeit.  
  151.              * Vagy ez már a kákán a csomó?  
  152.              */  
  153.   
  154.             function clone(obj) {  
  155.                 if ( obj == null || "object" != typeof obj) return obj;  
  156.                 var copy = {};  
  157.                 for (var attr in obj) {  
  158.                     if ( obj.hasOwnProperty(attr) )   
  159.                         copy[attr] = clone( obj[attr] );  
  160.                 }      
  161.                 return copy;  
  162.             }  
  163.               
  164.             var uni3 = clone(uni2);  
  165.               
  166.             uni.name = "Universe";  
  167.             uni3.name = "Black hole";  
  168.   
  169.             console.log( uni === uni2 );  
  170.             console.log( uni2 === uni3 );  
  171.             console.log( uni.name )  
  172.             console.log( uni2.name )  
  173.             console.log( uni3.name )  
  174.   
  175.         </script>  
  176.     </body>  
  177. </html>  
10

Pattern gyűjtemény

joed · 2012. Feb. 22. (Sze), 15.46
Már korábban is fogalmazódott bennem az ötlet, hogy milyen jó lenne egy weblaboros knowledge base, ahol minták, tippek és trükkök, esettanulmányok stb. hasznos cikkeket szednénk össze. A WL fórumokban is rengeteg ilyet találni, apró okosságokat, amik megkönnyíthetik a (leginkább kezdő programozó) ember életét. Lehetne beküldeni anyagot mint RFC, azt megszakértené itt a nyagyérdemű, aztán egy vote alapján mehetne a KB-be (vagy a kukába). A kritizálás mehetne itt a fórumokban, a KB gyűjtése meg akár egy GitHub repóba?
Hmm, vélemény?
11

Javaslatok a weblabor.hu

Hidvégi Gábor · 2012. Feb. 22. (Sze), 15.52
Javaslatok a weblabor.hu oldalhoz

Ha sokan támogatják, és nem bonyolult megvalósítani, akkor lehet rá számítani.
12

A Modult kicsit feltúrbóznám

Gixx · 2012. Feb. 22. (Sze), 16.10
... ha szabad :) Pont a múltkor kísérleteztem ezzel, és abba a problémába ütköztem, hogy miként lehet elérni publikus metódusból a privátot, illetve privátból a publikusat. Előbbi egyszerű, utóbbi kicsit trükkös.

Ím az eredmény (nem életszerű példa, csak techdemo):
  1. /** 
  2.  * Globális változók 
  3.  */  
  4. var a = 8;  
  5. var b = 9;  
  6. var c = 10;  
  7. var public = 888;  
  8.   
  9. /** 
  10.  * Globális függvények 
  11.  */  
  12.   
  13. function set(property, value) {  
  14.     alert(property + ' = ' + value);  
  15. }  
  16.   
  17. var MyClass = function(param) {  
  18.     /** 
  19.      * Privát változók 
  20.      */  
  21.     var a = 11;  
  22.     var b = 12;  
  23.     var c = null;  
  24.       
  25.     /** 
  26.      * Privát metódusok 
  27.      */  
  28.     var set = function(property, value) {  
  29.         eval(property + ' = ' + value);  
  30.     };  
  31.   
  32.     /** 
  33.      * pubikus objektum 
  34.      */  
  35.     var public = {  
  36.         /** 
  37.          * Publikus változók 
  38.          */  
  39.         d: 14,  
  40.         e: 15,  
  41.   
  42.         /** 
  43.          * Publikus metódusok 
  44.          */  
  45.         getPrivate: function(property) {  
  46.             // így érjük el a publikusból a privátot  
  47.             return eval (property);  
  48.         },  
  49.   
  50.         setPrivate: function(property, value) {  
  51.             // így érjük el a publikusból a privátot  
  52.             set(property, value);  
  53.         },  
  54.   
  55.         setGlobal: function(variable, value) {  
  56.             // így érjük el a publikusból a globálist  
  57.             eval('window.' + variable + ' = ' + value);  
  58.         }  
  59.     };  
  60.   
  61.     // a konstruktor így automatikusan lefut, ezért ennek kell az utolsónak lennie  
  62.     construct = function() {  
  63.         set('c', param);  
  64.           
  65.         // így érjük el privátból a publikust  
  66.         public.d = 14.1;  
  67.     }();  
  68.     return public;  
  69. };  
és akkor a Test case-ek:
  1. document.write(a);  
  2.   //-> 8  
  3. document.write(b);  
  4.   //-> 9  
  5. document.write(c);  
  6.   //-> 10  
  7. var MyClassInstance = new MyClass(13);  
  8. document.write(MyClassInstance.a);   
  9.   //-> 'undefined', mert ez privát változó  
  10. document.write(MyClassInstance.b);   
  11.   //-> 'undefined', mert ez privát változó  
  12. document.write(MyClassInstance.c);   
  13.   //-> 'undefined', mert ez privát változó  
  14. document.write(MyClassInstance.d);   
  15.   //-> 14.1, mert ez publikus változó (de a konstruktorban megváltozott)  
  16. document.write(MyClassInstance.e);   
  17.   //-> 15, mert ez publikus változó  
  18. document.write(MyClassInstance.getPrivate('a'));   
  19.   //-> 11  
  20. document.write(MyClassInstance.getPrivate('b'));   
  21.   //-> 12  
  22. document.write(MyClassInstance.getPrivate('c'));   
  23.   //-> 13, de ezt ugye a konstruktorunk állította be  
  24. MyClassInstance.setPrivate('c', 23);  
  25.   // nem lesz alert  
  26. document.write(MyClassInstance.getPrivate('c'));   
  27.   //-> 23  
  28. MyClassInstance.setPrivate('public.e', 211);  
  29.   // Miért ne? Bár sok értelme nincs :)  
  30. document.write(MyClassInstance.e);  
  31.   //-> 211  
  32. MyClassInstance.setGlobal('b', 1000);  
  33. document.write(a);  
  34.   //-> 8  
  35. document.write(b);  
  36.   //-> 1000  
  37. document.write(c);  
  38.   //-> 10  
  39. document.write(MyClassInstance.getPrivate('b'));   
  40.   //-> 12, ez ugye nem változott  
  41. document.write(typeof(public));   
  42.   //-> 'number', mert a globális változó nem változott  
  43. set('valami', 188);  
  44.   // Alert ablakban feljön, hogy "valami = 188"  
13

De tovább is lehetne vinni...

Gixx · 2012. Feb. 22. (Sze), 16.14
... ha a privát részeket is egy objektumba rakjuk, pl
  1. var MyClass = function(param) {  
  2.     /** 
  3.      * privát objektum 
  4.      */  
  5.     var private = {  
  6.         // ...  
  7.     };  
  8.   
  9.     /** 
  10.      * pubikus objektum 
  11.      */  
  12.     var public = {  
  13.         // ...  
  14.     };  
  15.   
  16.     construct = function() {  
  17.         // ...  
  18.     }();  
  19.     return public;  
  20. };  
14

Words reserved for possible future use

Poetro · 2012. Feb. 22. (Sze), 16.40
Ha lehet akkor kerüljük a foglalt szavak használatát, mint változónevek (public, private).
16

A private-re szerintem nincs

inf · 2012. Feb. 22. (Sze), 17.58
A private-re szerintem nincs valami nagy szükség, én remekül megvagyok nélküle... Ha valami olyan van, amit meg legszívesebben oda tennék, akkor valószínű, hogy új osztályt kell létrehozni neki, mert túl nagy az aktuális osztály felelősségi köre.
15

(A Singleton-nál van egy kis

inf · 2012. Feb. 22. (Sze), 17.55
(A Singleton-nál van egy kis elírás: "Singelon.metodus2(1,3)")

A Modul-nál mi szükség van a "new" kulcsszóra? (költői kérdés...) Persze díszítő szerepe lehet... A new mindig a konstruktor egy "példányát" adja vissza, kivétel ha megadunk a konstruktornak visszatérő értéket... Én jobb szeretem a prototípus alapú "modul" készítést, szerintem átláthatóbb, bár vannak hátrányai is.
17

Személyes kedvencem

kisPocok · 2012. Feb. 22. (Sze), 22.40
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth

üdv,
kisPocok
18

Örökzöld

Török Gábor · 2012. Feb. 23. (Cs), 00.59