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:

var SingletonNeve = {
  metodus1: function(p1, p2) {
    return p1+p2;
  },
  metodus2: function(p1, p2) {
    return SingletonNeve.tulajdonsag1 + ' ' + SingletonNeve.metodus1(p1, p2);
  },
  tulajdonsag1: "Hello, én vagyok a Singleton!",
  tulajdonsag2: "Hello Weblabor!"
}

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:


var ModulNeve = function(p1, p2){
  // privát metódusok
  function privat1(n){
    return "a végső válasz: "+n;
  }
  // privát változók
  var privat_valtozo1;
  // konstruktor
  privat_valtozo1 = p2 - p1 + 23;
  // publikus metódusok, változók
  return {
    publikus1: function() {
      return privat1(this.publikus_valtozo1 + privat_valtozo1);
    },
    publikus_valtozo1: 42
  }
};

var peldany = new ModulNeve(4, 8);
peldany.publikus_valtozo1 = 15;
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:
var Singleton = new function Singleton() {
  var valtozo = 'ertek';

  function privat_fuggveny() {
  }

  this.publikus_fuggveny = function publikus_fuggveny() {
    return valtozo;
  }
}

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:

var Singleton = new function() {
  var valtozo = 'ertek';

  function privat_fuggveny() {
  }

  this.publikus_fuggveny = function() {
    return valtozo;
  }
}

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.

var masikSingletonNeve = SingletonNeve;
masikSingletonNeve.metodus1();
6

Futtasd le

Bártházi András · 2012. Feb. 22. (Sze), 14.43
var Obj = { x: 1, y: 2 };
var ObjCopy = Obj;
ObjCopy.x = 3;
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:
<!doctype html>
<html lang="en">
	<head>
		<title>JavaScript Patterns</title>
		<meta charset="utf-8">
	</head>
	<body>
		<script>
			/* Title: Singleton
			 Description: restricts object creation for a class to only one instance
			 */

			var obj = {
				myprop:'my value'
			};

			var obj2 = {
				myprop:'my value'
			};
			obj === obj2; // false
			obj == obj2;  // false

			var uni = new Universe();
			var uni2 = new Universe();
			uni === uni2; // true

			function Universe() {

				// do we have an existing instance?
				if (typeof Universe.instance === 'object') {
					return Universe.instance;
				}

				// proceed as normal
				this.start_time = 0;
				this.bang = "Big";

				// cache
				Universe.instance = this;

				// implicit return:
				// return this;
			}

			// testing
			var uni = new Universe();
			var uni2 = new Universe();
			uni === uni2; // true

			/*** Instance in a Closure ***/

			function Universe() {

				// the cached instance
				var instance = this;

				// proceed as normal
				this.start_time = 0;
				this.bang = "Big";

				// rewrite the contructor
				Universe = function () {
					return instance;
				};
			}

			function Universe() {

				// the cached instance
				var instance;

				// rewrite the constructor
				Universe = function Universe() {
					return instance;
				};

				// carry over the prototype properties
				Universe.prototype = this;

				// the instance
				instance = new Universe();

				// reset the constructor pointer
				instance.constructor = Universe;

				// all the functionality
				instance.start_time = 0;
				instance.bang = "Big";

				return instance;
			}


			// testing
			var uni = new Universe();
			var uni2 = new Universe();
			uni === uni2; // true

			// adding to the prototype
			Universe.prototype.nothing = true;

			var uni = new Universe();

			// again adding to the prototype
			// after the initial object is created
			Universe.prototype.everything = true;

			var uni2 = new Universe();

			// only the original prototype was
			// linked to the objects
			uni.nothing; // true
			uni2.nothing; // true
			uni.everything; // undefined
			uni2.everything; // undefined

			// that sounds right:
			uni.constructor.name; // "Universe"

			// but that's odd:
			uni.constructor === Universe; // false

			var Universe;

			(function () {

				var instance;

				Universe = function Universe() {

					if (instance) {
						return instance;
					}

					instance = this;

					// all the functionality
					this.start_time = 0;
					this.bang = "Big";
				};
			}());

			// reference
			// http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript
			// http://shop.oreilly.com/product/9780596806767.do?sortby=publicationDate


			/**
			 * Új cucc a példához: jó hogy a konstruktor le van védve de egy jquery/mootools szerű clone
			 * ellen ez nem véd, és egy egységsugarú user összevissza használhatja a keretrendszer függvényeit.
			 * Vagy ez már a kákán a csomó?
			 */

			function clone(obj) {
			    if ( obj == null || "object" != typeof obj) return obj;
			    var copy = {};
			    for (var attr in obj) {
			        if ( obj.hasOwnProperty(attr) ) 
			        	copy[attr] = clone( obj[attr] );
			    }    
			    return copy;
			}
			
			var uni3 = clone(uni2);
			
			uni.name = "Universe";
			uni3.name = "Black hole";

			console.log( uni === uni2 );
			console.log( uni2 === uni3 );
			console.log( uni.name )
			console.log( uni2.name )
			console.log( uni3.name )

		</script>
	</body>
</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):

/**
 * Globális változók
 */
var a = 8;
var b = 9;
var c = 10;
var public = 888;

/**
 * Globális függvények
 */

function set(property, value) {
	alert(property + ' = ' + value);
}

var MyClass = function(param) {
	/**
	 * Privát változók
	 */
	var a = 11;
	var b = 12;
	var c = null;
	
	/**
	 * Privát metódusok
	 */
 	var set = function(property, value) {
		eval(property + ' = ' + value);
	};

	/**
	 * pubikus objektum
	 */
	var public = {
		/**
		 * Publikus változók
		 */
		d: 14,
		e: 15,

		/**
		 * Publikus metódusok
		 */
		getPrivate: function(property) {
			// így érjük el a publikusból a privátot
			return eval (property);
		},

		setPrivate: function(property, value) {
			// így érjük el a publikusból a privátot
			set(property, value);
		},

		setGlobal: function(variable, value) {
			// így érjük el a publikusból a globálist
			eval('window.' + variable + ' = ' + value);
		}
	};

	// a konstruktor így automatikusan lefut, ezért ennek kell az utolsónak lennie
	construct = function() {
		set('c', param);
		
		// így érjük el privátból a publikust
		public.d = 14.1;
	}();
	return public;
};
és akkor a Test case-ek:

document.write(a);
  //-> 8
document.write(b);
  //-> 9
document.write(c);
  //-> 10
var MyClassInstance = new MyClass(13);
document.write(MyClassInstance.a); 
  //-> 'undefined', mert ez privát változó
document.write(MyClassInstance.b); 
  //-> 'undefined', mert ez privát változó
document.write(MyClassInstance.c); 
  //-> 'undefined', mert ez privát változó
document.write(MyClassInstance.d); 
  //-> 14.1, mert ez publikus változó (de a konstruktorban megváltozott)
document.write(MyClassInstance.e); 
  //-> 15, mert ez publikus változó
document.write(MyClassInstance.getPrivate('a')); 
  //-> 11
document.write(MyClassInstance.getPrivate('b')); 
  //-> 12
document.write(MyClassInstance.getPrivate('c')); 
  //-> 13, de ezt ugye a konstruktorunk állította be
MyClassInstance.setPrivate('c', 23);
  // nem lesz alert
document.write(MyClassInstance.getPrivate('c')); 
  //-> 23
MyClassInstance.setPrivate('public.e', 211);
  // Miért ne? Bár sok értelme nincs :)
document.write(MyClassInstance.e);
  //-> 211
MyClassInstance.setGlobal('b', 1000);
document.write(a);
  //-> 8
document.write(b);
  //-> 1000
document.write(c);
  //-> 10
document.write(MyClassInstance.getPrivate('b')); 
  //-> 12, ez ugye nem változott
document.write(typeof(public)); 
  //-> 'number', mert a globális változó nem változott
set('valami', 188);
  // 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

var MyClass = function(param) {
	/**
	 * privát objektum
	 */
	var private = {
		// ...
	};

	/**
	 * pubikus objektum
	 */
	var public = {
		// ...
	};

	construct = function() {
		// ...
	}();
	return public;
};
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