sábado, 7 de junio de 2014

Javascript OOP, Visual Method - Part II

Previous post: Javascript OOP, Visual Method




Refactoring to preserve Liskov's Substitution Principle.
   Note: Due "super" is a reserved word, "soper" is its replacement.




/* IShape -- abstract class */
function IShape(obj) {
  if(obj.area && obj.perimeter) return obj;
  else return undefined;
};

/* Circle constructor */
function circle(radius) {
  return IShape( {
    "area": function() {
      return Math.PI * radius * radius;
    },
    "perimeter": function() {
      return 2 * Math.PI * radius;
    }
  });
};

/* Rectangle constructor */
function rectangle(length, width) {
  return IShape( {
    "area": function() { 
      return length * width;
    },
    "perimeter": function() {
      return (2 * length) + (2 * width);
    }
  });
};

/* Triangle constructor */
function triangle(base, height) {
  return IShape( {
    "area": function() {
      return base * height;
    },
    "perimeter": function() {
      return undefined;
    }
  });
};

/* Square -- child class -- constructor */
function square(side) {
  return IShape( {
    "soper": rectangle(side, side),
    "area": function() {
      return this.soper.area();
    },
    "perimeter": function() {
      return this.soper.perimeter(side, side);
    }
  });
};

/* Shapes factory */
function shapesFactory(shapeId) {
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle;
    case "rectangle":
      return rectangle;
    case "triangle":
      return triangle;
    case "square":
      return square;
    default:
      return undefined;
  }
};

// TESTS
var shape1 = shapesFactory("circle")(1);

console.log("Circle(radius=1)."+
   " Area: " + shape1.area() +
   " Perimeter: " + shape1.perimeter());
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = shapesFactory("rectangle")(2,3);

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area() +
   " Perimeter: " + shape2.perimeter());
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = shapesFactory("triangle")(2,3);

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area() +
   " Perimeter: " + shape3.perimeter());
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = shapesFactory("square")(1);

console.log("Square(radius=1)."+
   " Area: " + shape4.area() +
   " Perimeter: " + shape4.perimeter());
// output:
// Square(radius=1). Area: 1 Perimeter: 4

viernes, 6 de junio de 2014

Javascript OOP, Visual Method

Let's do this inheritance example with Javascript:



All objects must calculate area and perimeter through a common interface and must be generated through a factory.



/* IShape -- abstract class */
function IShape(area, perimeter) {
  return { "area":area, "perimeter":perimeter };
};

/* Circle constructor */
function circle() {
  var area = function(radius) {
    return Math.PI * radius * radius;
  };
  var perimeter = function(radius) {
    return 2 * Math.PI * radius;
  };
  return IShape(area, perimeter);
};

/* Rectangle constructor */
function rectangle() {
  var area = function(length, width) {
    return length * width;
  };
  var perimeter = function(length, width) {
    return (2 * length) + (2 * width);
  };
  return IShape(area, perimeter);
};

/* Triangle constructor */
function triangle() {
  var area = function(base, height) {
    return base * height;
  };
  var perimeter = function() {
    return undefined;
  };
  return IShape(area, perimeter);
};

/* Square -- child class -- constructor */
function square() {
  var extendedObject = rectangle();
  var area = function(side) {
    return extendedObject.area(side, side);
  };
  var perimeter = function(side) {
    return extendedObject.perimeter(side, side);
  };
  return IShape(area, perimeter);
};

/* Shapes factory */
function shapesFactory(shapeId) {
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle();
    case "rectangle":
      return rectangle();
    case "triangle":
      return triangle();
    case "square":
      return square();
    default:
      return undefined;
  }
};

// TESTS
var shape1 = shapesFactory("circle");

console.log("Circle(radius=1)."+
   " Area: " + shape1.area(1) +
   " Perimeter: " + shape1.perimeter(1));
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = shapesFactory("rectangle");

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area(2,3) +
   " Perimeter: " + shape2.perimeter(2,3));
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = shapesFactory("triangle");

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area(2,3) +
   " Perimeter: " + shape3.perimeter(2,3));
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = shapesFactory("square");

console.log("Square(radius=1)."+
   " Area: " + shape4.area(1) +
   " Perimeter: " + shape4.perimeter(1));
// output:
// Square(radius=1). Area: 1 Perimeter: 4




Using JQuery the factory could be an object on another file:


/* Shapes factory */
jQuery.shapesFactory = function(shapeId) {

  /* IShape -- abstract class */
  function IShape(area, perimeter) {
    return { "area":area, "perimeter":perimeter };
  };

  /* Circle constructor */
  function circle() {
    var area = function(radius) {
      return Math.PI * radius * radius;
    };
    var perimeter = function(radius) {
      return 2 * Math.PI * radius;
    };
    return IShape(area, perimeter);
  };

  /* Rectangle constructor */
  function rectangle() {
    var area = function(length, width) {
      return length * width;
    };
    var perimeter = function(length, width) {
      return (2 * length) + (2 * width);
    };
    return IShape(area, perimeter);
  };

  /* Triangle constructor */
  function triangle() {
    var area = function(base, height) {
      return base * height;
    };
    var perimeter = function() {
      return undefined;
    };
    return IShape(area, perimeter);
  };

  /* Square -- child class -- constructor */
  function square() {
    var extendedObject = rectangle();
    var area = function(side) {
      return extendedObject.area(side, side);
    };
    var perimeter = function(side) {
      return extendedObject.perimeter(side, side);
    };
    return IShape(area, perimeter);
  };

  /* Shapes factory */
  switch (shapeId.toLowerCase()) {
    case "circle":
      return circle();
    case "rectangle":
      return rectangle();
    case "triangle":
      return triangle();
    case "square":
      return square();
    default:
      return undefined;
  }
};

// TESTS - Another file -
var shape1 = $.shapesFactory("circle");

console.log("Circle(radius=1)."+
   " Area: " + shape1.area(1) +
   " Perimeter: " + shape1.perimeter(1));
// output:
// Circle(radius=1). Area: 3.141592653589793 Perimeter: 6.283185307179586 

var shape2 = $.shapesFactory("rectangle");

console.log("Rectangle(length=2, width=3)."+
   " Area: " + shape2.area(2,3) +
   " Perimeter: " + shape2.perimeter(2,3));
// output:
// Rectangle(length=2, width=3). Area: 6 Perimeter: 10

var shape3 = $.shapesFactory("triangle");

console.log("Triangle(base=2, height=3)."+
   " Area: " + shape3.area(2,3) +
   " Perimeter: " + shape3.perimeter(2,3));
// output:
// Triangle(base=2, height=3). Area: 6 Perimeter: undefined

var shape4 = $.shapesFactory("square");

console.log("Square(radius=1)."+
   " Area: " + shape4.area(1) +
   " Perimeter: " + shape4.perimeter(1));
// output:
// Square(radius=1). Area: 1 Perimeter: 4



Part II: Managing to preserve Liskov's Substitution Principle (LSP)

Manipular Funciones Javascript


Las funciones en Javascript se pueden manipular tal como se hace con las variables. He aquí unos ejemplos.

 
 var porDos = function(n) { return 2*n; };
 console.log("4 x 2 = "+porDos(4));

// Output:
// 4 x 2 = 8 

La función que multiplica un número N por dos se almacenó en la variable "porDos". Para evaluar la función se usó el nombre de la variable con el argumento definido en la declaración "function(n)".

Otro ejemplo:


 var porTres = function(n) { return 3*n; };
 console.log("4 x 3 = "+porTres(4));

// Output:
// 4 x 3 = 12

Cumplen las reglas de composición:


 var porCinco = function(n) {
  return porDos(n) + porTres(n);
 };
 console.log("4 x 5 = "+porCinco(4));

// Output:
// 4 x 5 = 20

 var porSeis = function(n) {
  return porDos(porTres(n));
 };
 console.log("4 x 6 = "+porSeis(4));

// Output:
// 4 x 6 = 24

Una función puede retornar otra función:


 var fabricaPorM = function(M) {
  return function(n) {
   return M*n;
  };
 };
 var porOcho = fabricaPorM(8);
 console.log("4 x 8 = "+porOcho(8));

// Output:
// 4 x 8 = 32

Una función se puede pasar como argumento:


  var multiplosHastaDiez = function(fnMultiplicar) {

     var valIni = fnMultiplicar(1);
     console.log("Multiplos de "+valIni+" del 1 al 10");

     for(var i=1;i<=10;i++) {
        console.log(""+i+" x "+valIni+" = "+fnMultiplicar(i));
     }
  }

Probando:


 multiplosHastaDiez(porCinco);

// Output:
// Multiplos de 5 del 1 al 10
// 1 x 5 = 5 
// 2 x 5 = 10 
// 3 x 5 = 15 
// 4 x 5 = 20 
// 5 x 5 = 25 
// 6 x 5 = 30 
// 7 x 5 = 35 
// 8 x 5 = 40 
// 9 x 5 = 45 
// 10 x 5 = 50 

También:


 multiplosHastaDiez(fabricaPorM(9));

// Output:
// Multiplos de 9 del 1 al 10
// 1 x 9 = 9
// 2 x 9 = 18
// 3 x 9 = 27
// 4 x 9 = 36
// 5 x 9 = 45
// 6 x 9 = 54
// 7 x 9 = 63
// 8 x 9 = 72
// 9 x 9 = 81
// 10 x 9 = 90 

miércoles, 2 de abril de 2014

PSP en tres minutos


Se encuentra con un cliente, es la tercera reunión, y éste pregunta: ¿Y qué es PSP?

Hace una pausa y medita para luego concluir que el plan de la reunión es defectuoso y que estrategia de comunicación hasta el momento ha fallado.

Toma nota mental y continúa la junta.

Una vez aclarada la duda del interlocutor, toma nota mental del tiempo transcurrido empleado en solucionarla.

Con estos datos se pueden modificar los planes y diseñar estrategias más efectivas de comunicación con los clientes.

Pero...
La memoria es selectiva. Muchas de esas notas se olvidan o cambian de prioridad según el contexto o el tiempo transcurrido.


La propuesta de PSP radica en emplear métodos formales para registrar esos datos, compararlos y luego tomar decisiones.

Para ello, al detectar el fallo:

Registra el evento de acuerdo a una clasificación estándar, incluye una pequeña descripción, inicia un cronómetro e inicia la reparación.

Una vez aclarado el problema, registra el tiempo efectivo empleado en la solución.

Un análisis estadístico de frecuencias determinará los problemas más comunes y el tiempo promedio que toma arreglarlos. Esto permitirá priorizar el esfuerzo en corregir los errores más costosos; los que en conjunto quitan más tiempo.

Pero...
Al modificar el proceso ¿Cómo determinar el efecto de las mejoras?


Además de estandarizar el registro de fallos, el proceso también debe ser monitoreado. Para ello, PSP divide el proceso de desarrollo de software en etapas, cada una de las cuales tiene sus propios indicadores de calidad.

Así, al hacer un ajuste y luego recopilar nueva información se tendrá una idea clara del impacto, positivo o negativo, sobre el proceso en general.


Para terminar, vale la pena aclarar que el método PSP, propuesto por Watts Humphrey para alcanzar los objetivos anteriores, es sumamente invasivo. Interviene activamente en cada etapa y rige gran parte del proceso, todo con el fin de obtener medidas precisas y confiables, indispensables para el análisis posterior. He ahí la razón de su complejidad.




lunes, 31 de marzo de 2014

Json to Dom with jQuery

jQuery plugin to fill in DOM nodes with JSON. Simpler and more flexible than most templating / binding engines:
https://github.com/gescript/json-to-dom


domingo, 9 de marzo de 2014

Versionar Base de Datos y versionar los datos de la DB


En #AgileOpenBogotá (estuvo buenísimo!) mientras Adrián Moya nos contaba sobre los métodos y herramientas automatizadas para operaciones basadas en las técnicas ágiles tales como TDD, BDD, ATDD, Integración Continua...etc. Surgió una duda sobre los problemas de versionado de la base de datos.

En el caso expuesto como problemático, la base de datos cambiaba con el tiempo, de tal manera que en poco tiempo los datos eran incompatibles entre versiones. El escenario se hacía más complejo al tener, obligatoriamente, que mantener diferentes versiones de la base de datos de manera concurrente.

El caso puntual: cuando es el cliente quién decide (o no) actualizar. Por ejemplo: un juego en línea descargado desde una tienda para dispositivos móviles, tabletas o pc's.

O un caso adicional: cuando la ley exige la integridad de esos datos (como en contabilidad) pero esa misma ley es cambiante en cuanto a métodos, fórmulas o restricciones; como por ejemplo una reforma tributaria. O simplemente la base de datos es muy grande.

Para hacerse una imagen del problema, piense en el software como una fábrica de televisores donde cada rutina es una máquina ensambladora. Cuando un televisor recién ensamblado presenta un defecto se puede encontrar la máquina que está fallando para ser reparada o reemplazada (trazabilidad interna) y el televisor se repara o desecha. Si el televisor está fuera de la fábrica; usando trazabilidad normal o inversa se obtiene el mismo resultado, depende de quién haya encontrado el defecto, ya sea el fabricante o el cliente.

Este ejemplo surge de las vacas locas donde la carne infectada debía ser retirada del comercio y la información de trazabilidad permitía encontrar el rancho de donde provenía.

Versionamos el software según sus partes o remiendos, pero no versionamos los datos.

Ese fenómeno puede bautizarse como las vacas locas del sofware. Tenemos software que consume productos de software. Pensar que en algún momento los datos se integrarán y las máquinas enloquecerán no es ficción, ocurre en las bolsas de valores a velocidades inimaginables y de forma compleja, dejando de paso pérdidas extraordinarias entre otros.

¿Qué hacer?

La explicación de Adrián (según entendí) solucionaba el problema registrando tanto la versión del software como la de la base de datos y migrando los datos al nuevo formato.

Pero este no es el caso. ¿Qué hacer si no es posible migrar los datos? ¿Hay una opción simple, barata, fácil de implementar y mantener?

Primera solución (ingenua y causa overhead.)

Agregar una columna a cada tabla donde se registra la versión del software que la elaboró (tal como se marca el televisor e inviolable como una llave primaria.) Será un valor diferente a la versión de la base de datos (un entero basta.)
Con esto al menos el software podría reaccionar ante registros desactualizados.

Otra solución (engorrosa y absurda.)

Crear una base de datos nueva cada vez que hay un cambio. Pero, un select o un join serían trabajo arduo, incluso imposible.


¿Qué otras soluciones hay?

En verdad hay muy poca información sobre trazabilidad normal e inversa en este campo (o no la supe hallar.)


Mientras tanto, el aviso "Hay una nueva versión del software, descárguela o asuma las consecuencias" es una opción "recomendable".


jueves, 27 de febrero de 2014

Modelo-Vista-Controlador (MVC) con JQuery - Ejemplo Básico

Este ejemplo de "hello world" sale de http://cakebaker.42dh.com/2007/03/17/mvc-with-javascript/ Lo que hice fue actualizarlo para JQuery.

--- HTML ---
  <a id="userEvent" href="#">
 notify user action to controller
  </a>

  <div id="feedBackMessages"> </div>


--- JavaScript ---
  var model = {}, view = {}, controller = {};
 
  model.getText = function () {
 return 'hello world';
  };

  view.showMessage = function (message) {
 $('#feedBackMessages').html(message);
  };

  controller.sayHelloWorld = function () {
 view.showMessage(model.getText());
  };
 
  $("#userEvent").click( function(event){
 event.preventDefault();
 controller.sayHelloWorld();
  });

sábado, 9 de febrero de 2013

KATA ARQUITECTURA


Sensei: Carlos Peix
Facilitador: Luis Mulato
Duración: 2 horas.
Lugar: Hackbo, Bogotá.

Por el número de participantes se dividió el ejercicio en dos grupos. Cada uno resolvería un problema diferente durante 40 minutos.
Ambos problemas coincidieron en tener múltiples usuarios y cobros en dinero aunque se trataban de negocios diferentes.
Un grupo centró su solución en cuatro aspectos: escalabilidad, concurrencia, consistencia (dinero) y seguridad. Se planteó que la latencia era un problema a la hora de lograr un sistema justo con igualdad de oportunidades. En la solución escogida participaban cuatro sistemas físicos independientes, uno de ellos, la comunidad, se resolvía usando una base de datos de grafos (Neo4J). Otros dos de los sistemas serían contratados y el centro de la solución usaría una base de datos tipo memcaché con un mecanismo sofisticado de backup. Se habló también de usar "event sourcing".
El otro grupo produjo una especie de sistema por "apartamentos" con una interfaz de administración donde la nube era protagonista. El caché fue un tema central de diseño y quedó por determinar si era central o distribuido.
Una vez vueltos a agrupar expusimos el problema, la solución y el estado de avance.
Sobre la caché del segundo ejemplo se propuso usar un proxi inverso a una aplicación de una sola página (SPA), como gmail por ejemplo.

Sobre Neo4j:

Sobre SPA:

Y sobre "event sourcing": 


Nota: La descripción de los problemas fue omitida para que el material pueda ser usado de nuevo.

Hice este ejercicio con el fin de promover la participación en este tipo de eventos

sábado, 21 de agosto de 2010

YUI 3 JavaScript Alloy Calendar Set MinDate

El post donde nace es (pero en español puedo ser más locuaz):
http://yuilibrary.com/forum/viewtopic.php?f=206&t=4122&start=0&sid=1ea15b5959719f92d8ab84a660bd4f80


Aclaro que se trata de un método de fuerza bruta para obtener un comportamiento que el objeto no proporciona. Así que debe considerarse como una herramienta de prototipado, sólamente para mostrar "el concepto".
El truco es destruir el objeto conservando su estado interno. En este caso, se trata de un objeto complejo: un widget en javascript que muestra un calendario. Pero puede ser cualquier cosa, desde un desarrollo propio hasta un objeto comprado por el que hay que esperar una actualización.

En este caso, el widget no provee (por ahora) una función que permita establecer minDate luego de haber dibujado el calendario. Aprovecharemos los eventos personalizados y funciones de YUI 3. También un mecanismo del mismo componente: un evento que informa una selección de fecha. Advierto que puede ser una patada para el browser si se usa en forma intensiva.
En este ejemplo, el calendario establece su propio minDate en un ciclo infinito.
//Alloy calendar
function newCalendar(oMinDate) {
 new A.Calendar({
  id: 'xxCal',
  trigger: '#input1',
  dateFormat: '%m/%d/%Y',
  setValue: true,
  minDate: oMinDate,
  maxDate: null,
  firstDayOfWeek: 0,
  on: {
   select: function (event) {
    A.fire('calMinDate:newMinDate', 
      event.date.formatted.toString());
   }
  }
 }).render()
}
//Destroy DOM elements and make new calendar object
function crtlCalendar(calMinDate) {
    A.Node.one('#xxCal').set('innerHTML', '');
    A.Node.one('#xxCal').remove();
    newCalendar(calMinDate);
}
//Our custom eventhandler
A.on('calMinDate:newMinDate', crtlCalendar);

//Now, our first object
newCalendar('08/24/2010');


El equivalente apropiado, si existiera (pero existirá), sería:

new A.Calendar({
 id: 'xxCal',
 trigger: '#input1',
 dateFormat: '%m/%d/%Y',
 setValue: true,
 minDate: oMinDate,
 maxDate: null,
 firstDayOfWeek: 0,
 on: {
  select: function (event) {
   this.setMinDate(
     event.date.formatted.toString());
  }
 }
}).render()


Este mecanismo tiene nombre, se llama "continuación".

Un experimento previo con YUI 3 es un MVC "hello world" que busca mostrar el concepto de Modelo Vista Controlador en forma concreta.

sábado, 14 de agosto de 2010

Parábola del Actor: el Acto termina

En el post anterior (Parábola del Actor y el Objeto) se planteó la idea de procesos ligeros e inmortales en memoria. Pero esta situación no es realista, estos procesos deben terminar algún día. La solución es simple: el proceso no se vuelve a llamar a sí mismo; el garbage colector se encarga del resto.
Por ejemplo estos dos mensajes terminan el proceso:

vendeDor(PrecioArticulo, CompradorActual, VentasLogradas) ->
 receive
  {despedido, Jefe } ->
   Jefe ! {enCurso, self(), CompradorActual },
   adios;
  {jubilado, Jefe } ->
   Jefe ! {enCurso, self(), CompradorActual },
   graciasADios
 end. 

La siguiente pregunta es:
¿Qué sucede si hay una actualización del módulo "vendedor"? ¿Se detiene todo para poderlo online?


Lo mejor sería reemplazar el código "in-runtime" (en caliente), finalizando las operaciones pendientes usando el código antiguo y atendiendo las nuevas con el más reciente. (En Erlang existe y es un tema avanzado, se llama "release handling".)




Estos dos conceptos implican una entidad superior que lo controla todo. Obviamente se trata de un principio de diseño llamado árbol de supervisión compuesto por trabajadores y supervisores.




Espero con esto haber despertado un poco de interes en esta tecnología.



Viene de: Parábola del actor y del objeto.


Relacionados:
Erlang: Programación funcional y concurrente.
Mapeo de actores a objetos.

jueves, 12 de agosto de 2010

Parábola del Actor y el Objeto

La idea de hacer este embrollo surge del artículo de Angel "Java" López Look, ma! No database!, pero es toda mi responsabilidad.

Supongamos que nos devolvimos en el tiempo en una pequeña tienda que vende un solo producto.

Hay un “desconta-dor” (viejo mañoso y malhumorado) que calcula el descuento de cada venta, agrega el valor al recibo y le pone un sello. A continuación el “vende-dor” envía el recibo al “autoriza-dor” por otro sello, y por último, este le envía una copia a el “bodegista-dor”. Así, el “bodegista-dor” descuenta la cantidad de artículos vendidos (y pone un sello) y los entrega (otro sello)... etc.

Supongamos que cada uno de estos señores “-dor” tiene twitter (o un mensajero) y emite sus acciones como alienado (una por vez). Supongamos también que hay un fan de los “-dor” llamado “conta-dor”, encargado de fiscalizar el dinero y los artículos vendidos, y que no pierde un mensaje.

Supongamos también que al “vende-dor” no le cae bien el “bodegista-dor” y husmea su canal llevando la cuenta de las ventas efectivamente entregadas (la comisión depende de ello). Y como la desconfianza es algo contagioso, el  “autoriza-dor” hace lo mismo.
Agreguemos varios “vende-dor” (para hacer esto ameno) con su propia identificación (id) y hagamos que sigan la rutina del primero.

Si esto fuera orientado a objetos, cada “compra-dor” creará sus propios “-dor” usando la taxonomía de la tienda (siendo él mismo un “-dor”). Estos clones de los “-dor” existirán mientras sean necesarios (ocupando un lugar en la memoria de la tienda) y serán desechados luego de registrar sus actos; excepto el “conta-dor” que existe sólo los jueves y se encarga de analizar todos los registros de la semana (la tarde completa) y luego entrega su informe para dejar de existir.

Antes de continuar, la notación Erlang para hablar de Actores:
    ->             Inicio de rutina (entonces).
    {}             Tupla
    []             Lista
    atom        Etiqueta (empieza en minúsculas.)
    Var          Variable (empieza en mayúsculas.)
    !               Send()
    self()   Devuelve mi identificador (algo parecido a un número).

Cualquiera entiende este mensaje:
  { quiero, YO, "Videojuego", "Nientiendo" }

Enviar mensaje a Papá:
  Papa ! { quiero, self(), "Videojuego", "Nientiendo" }

Donde “Papá” es un identificador de proceso (Pid).

El padre, que tiene varios hijos y no sabe cual de ellos manda el mensaje, responde con un mensaje genérico:
  Hijo ! { noHayDinero }

Uniendo ambos mensajes, el mailbox de Papá será:
receive
  { quiero, Hijo, Articulo, Marca } ->
     Hijo ! { noHayDinero }
end.

El resultado de self() se asigna a la variable Hijo mediante Pattern Matching. En Artículo queda "Videojuego" y en Marca queda "Nientiendo".


Volviendo a la tienda, el mensaje entre uno de los "vendedor" y el "descontador" será:
descontador ! { descontar, self(), Cantidad, Valor }

El descontador captura el Pid del vendedor en la variable Vendedor, hace un proceso y le devuelve otro mensaje:
receive
  {descontar, Vendedor, Cantidad, Valor } ->
      ValorConDescuento = Valor * (1 - PorcentajeDescuento),
      Vendedor ! {descuento, Cantidad, ValorConDescuento }
end.

También debe notificar al "contador" y seguir existiendo, razón por la cual se llama a sí mismo manteniendo su estado interno (el porcentaje de descuento para el artículo). El "descontador" sería:
descontaDor(PorcentajeDescuento) ->
  receive
     {descontar, Vendedor, Cantidad, Valor } ->
        ValorConDescuento = Valor * (1 - PorcentajeDescuento),
        Vendedor ! {descuento, Cantidad, ValorConDescuento },
        contador ! {descuento, Cantidad, ValorConDescuento },
        descontaDor(PorcentajeDescuento)
  end.

%% Vendedor es una variable, al igual que Cantidad, Valor y ValorConDescuento.
%% "contador" y "descuento" son etiquetas

Los "vendedor" también se comunican con el "autorizador" y el "bodegizador". Una idea simple del código para este actor:
vendeDor(PrecioArticulo, CompradorActual, VentasLogradas) ->
  receive
    {iniciarVenta, Comprador, Cantidad } ->
       %% Se omite la lógica para atender un comprador a la vez
       Valor = Cantidad * PrecioArticulo,
       descontador ! {descontar, self(), Cantidad, Valor},
       vendeDor(PrecioArticulo, Comprador, VentasLogradas);
    {descuento, Cantidad, Valor } ->
       autorizador ! {autorizar, Cantidad, Valor},
       vendeDor(PrecioArticulo, CompradorActual, VentasLogradas);
    {finVenta, _, _ } ->
       CompradorActual ! {retirarMercancia },
       vendeDor(PrecioArticulo, _, VentasLogradas + 1)
 end.
%%Donde “_” significa no importa o variable vacía.

El mensaje “iniciarVenta” llega de un “comprador” y el mensaje “finVenta” del “bodegador”.

Para “lanzar” estos procesos (algo como un constructor), se usa spawn (desovar), así:
   Pid_descontador = spawn(descontaDor, [0.01]).
   Pid_vendedor = spawn(vendeDor, [20.50, _, 0]).

Y como “descontador” solo hay uno, lo registramos con una identidad única (lo mismo con  autorizador, bodegador y contador):
   register(descontador, Pid_descontador).
   ...
   register(contador, Pid_contador).

Por esta razón, tanto el “vendedor” como el "descontador" no necesitaron los Pids para enviarles mensajes.

Luego de esta maraton, quedan de ejercicio mental los demás actores. La idea general, es que estos actores existen siempre en memoria, atendiendo y enviando mensajes sin necesidad de ser instanciados y destruidos cada vez, y ocupando muy poco espacio en memoria. Son concurrentes, asincrónicos e independientes, pues no comparten información de estado.

Volviendo al artículo de Angel que originó todo esto: ¿para qué la Base de Datos? Y me respondo, tal vez como un histórico, como un almacén de comprobantes (recibos y sellos de la tienda).


Para quién quiera explorar el origen (o entender de verdad): Erlang.

Sigue con: El acto termina.

Relacionados:
Erlang: Programación funcional y concurrente.
Mapeo de actores a objetos.

Erlang: Mapping Objects Classes to Actors Model

You can "map" an object class to actor. Lets see a simple example:

public class myClass
{
   private string myProperty;

   public myClass(string argInit) {
      myProperty = argInit;
   }

   public myMethod(string arg1, int arg2) {
      //do something
      myProperty = arg1 + arg2.toString();
   }
}

The actor (process with Erlang) is:

myClass(myProperty) ->
   receive
      {myMethod, Arg1, Arg2 } ->
         myClass(Arg1 ++ Arg2)
   end.

And it's "constructor" is the spawn of process:

spawn( myClass, [argInit ]).

Thus, an object's method (OOP) is an actor's message. Like UML sequence diagrams.

lunes, 9 de agosto de 2010

Erlang y OOP

Mi primera experiencia de programación fue con Erlang, un lenguaje de programación orientado a la concurrencia (Concurrency Oriented Programming) y cuyo énfasis es la confiabilidad. Se trata de un lenguaje funcional.
El modelo de memoria está basado en "tail-call optimization" de unidades con estado llamadas "procesos". Estos procesos no guardan información de estado compartida, comunicándose únicamente mediante mensajes. Si un proceso es modificado y algo falla, se restituye a su estado previo (parecido a la destrucción y vuelta a crear del objeto, pero en memoria.)
Estas características permiten actualizaciones en caliente de la aplicación, cosa que no se ve en los ambientes OOP.

Sin entrar a realizar una introducción al lenguaje; existe una cierta correspondencia entre los modelos Erlang y OOP. Por ejemplo, una clase con propiedades y métodos:

public class myClass
{
   private string myProperty;

   public myClass(string argInit) {
      myProperty = argInit;
   }

   public myMethod(string arg1, int arg2) {
      //do something
      myProperty = arg1 + arg2.toString();
   }
}

En un proceso en Erlang:

myClass(myProperty) ->
   receive
      {myMethod, Arg1, Arg2 } ->
         myClass(Arg1 ++ Arg2)
   end.

Cuyo constructor queda implícito en el desove (spawn) del proceso:

spawn( myClass, [argInit ]).

Asi que un método de un objeto (OOP) es un mensaje en Erlang, tal como se describe en los diagramas de secuencia UML.

viernes, 6 de agosto de 2010

YUI 3: Modelo-Vista-Controlador (MVC) con JavaScript

Este ejemplo de "hello world" sale de http://cakebaker.42dh.com/2007/03/17/mvc-with-javascript/ Lo que hice fue actualizarlo para YUI 3.

--- HTML ---
<a id="bubbleUserEvent" href="javascript:void(0);">
   notify user action to controller 
</a>
<div id="feedBackMessages"> </div>


--- JavaScript ---
 YUI().use('event', function (Y) {
  var model = {
   getText: function () {
    return 'hello world';
   }
  };
  var view = {
   showMessage: function (message) {
    Y.Node.one('#feedBackMessages').set('innerHTML', message);
   }
  };
  var controller = {
   sayHelloWorld: function () {
    view.showMessage(model.getText());
   }
  };
  //Add event handler to view's control
  Y.on('click', controller.sayHelloWorld, '#bubbleUserEvent');
});


jueves, 15 de julio de 2010

Capas de Software

Sostengo que el concepto de "tres capas"(three-tier) o "n capas" (n-tier) de Software es más una metodología que una arquitectura

Para explicar la premisa anterior, me baso en el mismo simil arquitectónico que la origina. Veamos:

Un albañil, con la experiencia acumulada y si conoce lo esencial, puede llegar a construir una casa: muros, cimientos, ventanas, tuberías, conductos... etc. No sería una casa "mal hecha" porque no se cae, tiene habitaciones donde poner las camas, electricidad, agua, corredores y todo lo demás.
En el caso del Software es igual: tiene un GUI, una capa de negocio (que puede estar alojada en el servidor) y una DB para la persistencia. Con esto funciona.

Ahora bien, en mi concepto, el acto arquitectónico es un acto deliberado de creación sobre el espacio que busca incluir lo estético y lo estructural. Esto con el fin de obtener un espacio vital agradable y armónico.

Conocemos viviendas con corredores estrechos, cuartos oscuros, columnas mal dispuestas, escaleras potencialmente letales, baños que miran a la puerta de la calle... etc. Todas ellas habitadas y funcionando. Tienen techo, dos o tres pisos y pagan impuestos. Pasa igual con edificios completos y con conjuntos residenciales. Y todos ellos cumplen con ciertos mínimos que nos sirven para identificarlos como viviendas. Mínimos como: acceso a la calle, algunos servicios domiciliarios, techo y cerramiento. Esta situación es idéntica en el ámbito del Software.

Una casa (edificio o conjunto) diseñada por un arquitecto (bueno) tendrá los mismos elementos y funcionará como un todo armónico, facilitando la vida de los habitantes, de la comunidad y de los proveedores de servicios.


En conclusión, Larman dice que se trata de un patrón arquitectónico, pero en este contexto (y abusando del simil) la separación en capas sería más bien un patrón urbanístico.

lunes, 13 de octubre de 2008

Obtener un registro al azar desde un archivo Access(.mdb)

//Iniciar el generador pseudo-aleatorio
Int32 iPreSeed = Convert.ToInt32(DateTime.Now.Ticks % Int32.MaxValue);
Random fixRand = new Random((new Random(iPreSeed)).Next());

//Establecer la cadena de conexión
String ConnectionString = "Provider=Microsoft.Jet.OleDb.4.0;Data Source=" +
Server.MapPath("~/App_Data/test.mdb") + ";";

//Crear el objeto que conecta a la DB en Access
OleDbConnection _Connection = new OleDbConnection(ConnectionString);

try
{
//Abrir la conexión. Suena algo oscuro pero tiene que ver con la
//cantidad de usuarios concurrentes que pueden acceder a la DB.
//Es un bien preciado que no debe malgastarse.
_Connection.Open();

//Comando SQL a ejecutar. Solo obtiene los IDs de la tabla.
//Nota: Esta no es una práctica recomendable. Se usa para 
//          ejemplificar. Ver más abajo como usar un Store Procedure.
OleDbCommand _Command = new OleDbCommand("SELECT TestID FROM Test", _Connection);

//Ejecutar el comando SQL
OleDbDataAdapter myAdapt = new OleDbDataAdapter(_Command);

//Obtener el resultado del SQL en un objeto Tabla
DataTable myDataTable = new DataTable();
myAdapt.Fill(myDataTable);

//Obtener un ID al azar de la Tabla
int iRndTestID = fixRand.Next(0, myDataTable.Rows.Count -1);
string testRndID = myDataTable.Rows[iRndTestID][0].ToString();

//Nueva consulta SQL para obtener el registro elegido al azar.
//Usa un Store Procedure en lugar de un SQL.
_Command = new OleDbCommand("[TestByID]", _Connection);
_Command.CommandType = CommandType.StoredProcedure;
_Command.Parameters.AddWithValue("@testID", testRndID);

myAdapt = new OleDbDataAdapter(_Command);

myDataTable = new DataTable();
myAdapt.Fill(myDataTable);
}
catch (Exception Err){}
finally
{
//No olvidar cerrar la conexión.
_Connection.Close();
}

//Store procedure TestByID usado en el paso anterior
--------------[TestByID] Query--------------------

SELECT TestID
FROM Test
WHERE TestID = @testID

---------------------------------------------------

//CONCLUSIONES
//Se recomienda usar Store Procedures (SP) en lugar de sentencias
//SQL debido a que permite cambiar el programa o la base de datos
//sin muchos contratiempos. Si el programa conoce la estructura de la
//base de datos quedará ligado a ella. Esto se conoce como
//acoplamiento y debe ser reducido lo más posible. Al usar SPs se
//cumple con mantener el acoplamiento bajo.

viernes, 19 de septiembre de 2008

Random C#


Int32 iPreSeed = Convert.ToInt32(
          DateTime.Now.Ticks % Int32.MaxValue);
Random fixRand = new Random(
          (new Random(iPreSeed)).Next());
int iRndValue = fixRand.Next();


Se llama dos veces a Random para asegurarse que el generador pseudo-aleatorio quede bien iniciado.