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