Skip to content

Latest commit

 

History

History
116 lines (82 loc) · 5.64 KB

06. Modularización.md

File metadata and controls

116 lines (82 loc) · 5.64 KB

6. Modularización

Uno de los grandes problemas que existe con JavaScript, ergo con CoffeeScript, es que no importa el numero de ficheros que tengas tu proyecto. JavaScript solo sabe de lineas y por lo tanto existe la posibilidad de que El Libre Albedrío haga acto de presencia, creando estructuras incorrectas, inmantenibles y peligrando el GlobalScope. Se que esto puede darse en todos los lenguajes de programación, pero bajo mi punto de vista JavaScript Todos estos problemas crecen exponencialmente cuando es un equipo el que se enfrenta a un proyecto en JavaScript/CoffeeScript, tenemos/tienes que solucionarlo.

En este capítulo he intentado reunir algunos puntos interesantes que tienes que tener en cuenta para tener un código organizado y mantenible; independientemente del patrón de diseño que utilices.

6.1 Namespacing

Como ya vimos en capítulos anteriores, todas las variables que se declaran en una función solo existen en el ámbito propio de la función. Cada nuevo fichero que compila CoffeeScript a su vez genera una función autoejecutable, con su propio ámbito, y por lo tanto si tenemos dos ficheros las variables de un fichero no serán visibles desde el otro, a menos que estén declaradas en el GlobalScope. Por una parte esta bien pensado ya que a menos que lo queramos hacer consecuentemente, tu GlobalScope quedará limpio y sin saber de tus mil y una funciones.

Bien, y ahora te pregunto ¿cómo podemos acceder a esas funciones y variables que tenemos en módulos separados y fuera del GlobalScope?; muy sencillo. Debemos declarar una única variable global, en browsers asignarla al objeto window y en proyectos NodeJS al objeto global. Veamos como:

(global or window).libjs = {}

Tan sencillo como lo que ves, nos aseguramos que nuestro objeto libjs va a declararse en entornos browser o NodeJS. A partir de ahora ya podemos crear nuestros diferentes módulos utilizando el namespace libjs:

math.coffee

libjs.math =
  sum : (a, b) -> a + b
  rest: (a, b) -> a - b
  # ... # 

constants.coffee

libjs.CONST = 
  MIN_VALUE: 10
  MAX_VALUE: 90

class.coffee

class libjs.Hero

Este es un primer acercamiento a como tener organizados tus fuentes creando un sistema de Namespaces, de todas formas, te recomiendo que junto al Namespacing deberás utilizar algún patrón de diseño; siempre adecuado a la naturaleza de tu proyecto.

6.2 Mixins

Los llamados mixins no están soportados de forma nativa en CoffeeScript, por la sencilla razón de que son triviales y cada desarrollador hace su propia implementación; algo parecido a lo que pasa con el patrón MVC. En el siguiente ejemplo

extend = (obj, mixin) ->
  obj[name] = method for name, method of mixin obj

include = (class_reference, mixin) -> 
  extend class_reference.prototype, mixin

include Parrot, isDeceased: true
(new Parrot).isDeceased

Los mixins son un buen patrón para compartir lógica de negocio común entre diferentes módulos de tu aplicación. La ventaja de los mixins es que puedes incluirlo en más de un objeto o clase, mientras que comparandolo con la herencia de una clase solo puede heredar de un único origen.

6.3 Extendiendo clases

Una cosa que aprendí cuando lei mi primer libro sobre CoffeeScript, escrito por Alex MacCaw, fue que los mixins no son suficientes ya que no estan muy orientados a objetos. Necesitamos una mejor manera de integrar mixins en nuestras clases CoffeeScript, y Alex pensó en crear una clase base Module de la que el resto de clases extendiensen. La transcribo tal cual el lo pensó:

KEYWORDS = ['extended', 'included']

class Module
  @extend: (obj) ->
    for key, value of obj when key not in KEYWORDS 
      @[key] = value
    obj.extended?.apply(@)
    @

  @include: (obj) ->
    for key, value of obj when key not in KEYWORDS
      @::[key] = value
    obj.included?.apply(@)
    @

Como vemos crea dos funciones estáticas extend e include las cuales reciben un objeto, que en este caso será una secuencia de nombres de función y su función. La función extend se encargará de generar las funciones estáticas y la función include las funciones de instancia, facil. Ahora vamos a ponerlo en práctica, imagina que tienes un modulo que genera IDs aleatorios:

libjs.guid.coffee

libjs.guid = 
  generate: ->
    'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace /[xy]/g, (c) ->
      r = Math.random() * 16 | 0
      v = if c is 'x' then r else r & 3 | 8
      v.toString 16
    .toUpperCase()

El módulo libjs.guid y su función generate lo vamos a incluir dentro una nueva clase Hero extendida de Module, y la vamos a utilizar en el constructor que se encargará de con cada instancia tenga un atributo id que lo identifica:

class Hero extends Module
  @include libjs.guid
  
  constructor: (@name) ->
    @id = @generate()
    console.log "Instance with id #{@id}"

Parece sencillo no?, vamos a ponerlo en práctica creando un par de instancias:

batman = new Hero "Batman"
> 'Instance with id 04C1E095-E1B9-475F-9D8D-48BD931AAD04'

superman = new Hero "Superman"
> 'Instance with id 1EC75599-6CA7-490B-B3C1-F68CF4688814'

Como vemos la clase Module que pensó Alex McCaw es tremendamente fácil de implementar y extensible horizontalmente ya que puedes tener modulos iguales que se incluyen en clases diferentes, generando un código así mucho más mantenible. Por ponerte un ejemplo mis proyectos Monocle y Atoms utilizan la class Module y utilizan modulos comunes, a pesar de proyectos diferentes.