Uno de los patrones que más utilizo para el desarrollo de pequeñas aplicaciones, es el patrón "Revealing Module". Me gusta porque te permite encapsular funciones públicas y privadas, y dejar el global scope limpio.
Sin embargo, bajo determinadas circunstancias, dicho patrón puede quedarse pequeño, especialmente si nos gusta modularizar, u organizar el código de un modo más fácil de cara a mantenerlo y a futuras expansiones.
Imaginemos que tenemos una web con tabs, menus y un par de carruseles. Podríamos organizar nuestro código de muchas maneras. Una de ellas es usando el patrón "Module":
var AppUI = { Tabs: { show: function(){}, hide: function(){}, init: function(){} }, Menus: { show: function(){}, toggle: function(){}, Options: { add: function(){}, remove: function(){} }, init: function() {} }, Carruseles: { start: function(){}, stop: function(){}, next: function(){}, prev: function(){}, init: function(){} } };
La ventaja de este sistema, es que tengo el código organizado, y el global scope sólo se ve afectado por una nueva variable, o namespace, llamada AppUI, pero todas las funciones dentro de AppUI son públicas, lo que no es muy correcto. Fíjate que por error, yo podría escribir AppUI.Tabs.show(), en vez de AppUI.Menus.show(), y estar obteniendo una funcionalidad indeseada. Lo cual, podría arreglarlo escribiendo nombres de propiedades más descriptivas, como Tabs.showTabs(), o Menus.showMenus(), de tal modo que no pudiese cometer el error anterior, pero esto no deja de tener cierta redundancia, y con objetos con muchas propiedades, me obligaría a poner nombres quizá demasiado extensos, para hacerlos más descriptivos, y puede que perdiese algo de legibilidad.
No digo que lo anterior sea incorrecto, todo depende de la solución que más se adapte a nuestras necesidades. Si lo que queremos es evitar que todo sea público dentro de AppUI, lo anterior no es la mejor manera.
Me gusta que el global scope sólo se vea afectado por una única variable nueva, AppUI. Pero quiero evitar que dentro de ella, todas las funciones sean públicas. Es decir, quiero mantener lo más limpio posible, el scope de AppUI. ¿Cómo resolverlo? Pues aquí es donde entran en juego otros patrones, en concreto el "Revealing Module".
Refactorizando el anterior objeto AppUI, para adaptarlo a dicho patrón, nos quedaría algo como esto:
var AppUI = {}; AppUI = (function(){ function init(){} return { init:init } })(); AppUI.Tabs = (function(){ function show(){} function hide(){} function init(){} return { init:init } })(); AppUI.Menus = (function(){ function show(){} function toggle(){} function init(){} return { init:init } })(); AppUI.Menus.Options = (function(){ function add(){} function remove(){} return { add:add, remove:remove } })(); AppUI.Carruseles = (function(){ function start(){} function stop(){} function next(){} function prev(){} function init(){} return { start:start, stop:stop, init:init } })();
Lo que tenemos ahora es un patrón "Module", con varios patrones "Revealing Module" en su interior. Nos vamos acercando. Mantenemos la modularidad, ya que AppUI.Carruseles, así como el resto, los puedo definir en archivos distintos. Y hemos conseguido ocultar algunas funciones, haciéndolas privadas para el global scope y nuestro namespace. Por ejemplo, AppUI.Carruseles.next() es totalmente invisible. Sin embargo, me sigue sin gustar... Desde el global scope puedo acceder a todos los objetos de AppUI: Tabs, Menus y Carruseles. Desde AppUI.Carruseles, puedo acceder a AppUI.Menus.Options.add(), etc., y puede que no me interese.
Entonces se me ocurrió anidar el patrón "Revealing Module", dentro de otro "Revealing Module". Esto solo puede ser fruto de una mente retorcida... Veamos el resultado:
var AppUI = (function(){ var Tabs = (function(){ function show(){} function hide(){} function init(){ show(); } return { init:init } })(); var Menus = (function(){ var Options = (function(){ function add(){} function remove(){} function init(){ add(4); } return { init:init } })(); function show(){} function toggle(){} function init(){ show(); } return { init:init } })(); var Carruseles = (function(){ function show(){} function start(){} function stop(){} function next(){} function prev(){} function init(){ show(); start(); } return { init:init } })(); function init(){ Tabs.init(); Menus.init(); Carruseles.init(); } return { init:init } })();
De este modo, lo único que es visible desde el global scope, es AppUI.init(). Y por supuesto, desde Carruseles, no puedo ver a Menu.Options, como sucedía antes. A lo sumo, desde AppUI, tendría acceso únicamente a las funciones de Tabs, Menus y Carruseles, que hayamos querido hacer públicas, como por ejemplo, Menus.init(). Pero nada más.
El inconveniente, es que no podría dividirlo en varios archivos, por lo que hemos perdido toda modularidad: mal. Me sigue sin servir. Aunque puede que esto no suponga un inconveniente para el proyecto en el que estés...
Este patrón anidado, no es ni mejor ni peor que otros. Todo depende de lo que necesites, y supone simplemente una vuelta de tuerca más, para aquellos que sean un poco maniáticos con la limpieza del scope, en todos sus ámbitos. A mi personalmente me gusta más un namespece muuuuy limpio.
Si lo único que necesitamos es un lugar donde encapsular e inicializar todo un conjunto de funciones estancas entre ellas, pero mantener el scope lo más limpio posible, nos sobra más de la mitad del código anterior, y nos serviría con:
var AppUI = function(){ (function Tabs (){ function show(){} function hide(){} show() })(); (function Menus (){ (function Options (){ function add(){} function remove(){} add(4); })(); function show(){} function toggle(){} show(); })(); (function Carruseles (){ function show(){} function start(){} function stop(){} function next(){} function prev(){} show(); start(); })(); }
Y cuando queramos iniciarlizar nuestra función, por ejemplo, una vez cargado el DOM:
AppUI();
No hay comentarios :
Publicar un comentario