Mostrando entradas con la etiqueta traducción. Mostrar todas las entradas
Mostrando entradas con la etiqueta traducción. Mostrar todas las entradas

3 de octubre de 2013

Principios SOLID con JavaScript: El principio de inversión de dependencias (Traducción del Inglés)


En mi afán de aprender bien JavaScript, y ya de paso un poquito de inglés y un poquito de Ingeniería del Software, me he propuesto traducir una serie de artículos sobre los principios SOLID aplicados a JavaScript. El quinto de ellos es: SOLID JavaScript: The Dependency Inversion Principle.


Esta es la quinta y última de la serie "SOLID JavaScript" que explora los principios de diseño SOLID dentro del contexto del lenguaje JavaScript. En esta última entrega, vamos a examinar el principio de inversión de dependencias.

El principio de inversión de dependencias


El principio de inversión de dependencias se refiere a la estabilidad y la capacidad de reutilización de los componentes de alto nivel dentro de una aplicación. El principio establece:

A. Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.
B. Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.

La principal preocupación del principio de inversión de dependencias es garantizar que los principales componentes de una aplicación o framework permanecen desacoplados de los componentes auxiliares que suministran los detalles de implementación de bajo nivel. Esto asegura que las partes importantes de una aplicación o framework no se vean afectados cuando los componentes de bajo nivel tengan que cambiar.

La primera parte del principio se refiere al método de acoplamiento entre los módulos de alto nivel y los módulos de bajo nivel. Con arquitectura de capas tradicionales, los módulos de alto nivel (los componentes que encapsulan la lógica de negocio de la aplicación) tienen dependencias en módulos de bajo nivel (componentes que proporcionan temas de infraestructura). Cuando se adhiere al principio de inversión de dependencias, esta relación se invierte. En vez de que sean los módulos de alto nivel los que estén acoplados a los módulos de bajo nivel, deberían ser los módulos de bajo nivel los que se acoplen a las interfaces declaradas por los módulos de alto nivel. Por ejemplo, supongamos una aplicación con temas de persistencia, pues en un diseño tradicional puede pasar que un módulo principal esté basado en el API definido por un módulo de persistencia. Si refactorizamos para que se ajuste al principio de inversión de dependencias, el módulo de persistencia debería modificarse para que se ajustase a una interfaz definida por el módulo principal.

La segunda parte del principio de inversión de dependencias se refiere a la relación adecuada entre abstracciones y detalles. Para entender esta parte del principio, es útil considerar su aplicación en el lenguaje en donde el principio fue concebido: el lenguaje C++.

A diferencia de algunos lenguajes de tipado estático, C++ no proporciona a nivel del lenguaje la construcción de definición de interfaces. Lo que sí que proporciona es la separación de la definición de la clase de su implementación. En C++, las clases se definen mediante un archivo cabecera que enumera los métodos y variables miembros de una clase, junto con un archivo de código fuente que contiene la implementación de cualquier método miembro. Dado que las variables miembro y los métodos privados se declaran en el archivo de cabecera, es posible que las clases destinadas a ser usadas como abstracciones se vuelvan dependientes de los detalles de implementación de la clase. Esto se supera definiendo clases que sólo contienen métodos abstractos (conocido en C++ como clases base abstractas puras) para servir como interfaces para la implementación de clases.

DIP y JavaScript


Como es un lenguaje dinámico, JavaScript no requiere el uso de abstracciones para facilitar el desacoplamiento. Por lo tanto, la estipulación de que las abstracciones no deben depender de los detalles no es particularmente relevante para aplicaciones de JavaScript. Sin embargo, la estipulación de que los módulos de alto nivel no deben depender de módulos de bajo nivel si que lo es.

Al hablar del principio de inversión de dependencias en el contexto de lenguajes de tipado estático, la preocupación de acoplamiento es a la vez semántica y física. Es decir, si un módulo de alto nivel está acoplado a un módulo de bajo nivel, este está acoplado tanto a la interfaz de semántica, así como a la definición física de la interfaz definida en el módulo de bajo nivel. Esto implica que las dependencias del módulo de alto nivel deben invertirse tanto para las dependencias sobre bibliotecas de terceros, como las de módulos nativos de bajo nivel.

Para explicarlo, considera una aplicación .Net que puede encapsular módulos útiles de alto nivel que tienen una dependencia de un módulo de bajo nivel que proporciona temas de persistencia. Aunque es probable que el autor haya expuesto un API similar a la interfaz de persistencia, ya sea respetado el DIP o no, el módulo de alto nivel no podrá ser reutilizado en otra aplicación sin llevar consigo la dependencia del módulo de bajo nivel en donde se define la interfaz de persistencia.

En JavaScript, la aplicación del principio de inversión de dependencias sólo es relevante para el acoplamiento semántico entre módulos de alto nivel y módulos de bajo nivel. Así pues, la adhesión al DIP puede lograrse simplemente expresando la interfaz semántica en términos de necesidades de la aplicación y no mediante el acoplamiento de la interfaz implícita definida por alguna de las implementaciones elegidas para un módulo de bajo nivel .

Para ilustrarlo, consideremos el siguiente ejemplo:

$.fn.trackMap = function(options) {
    var defaults = { 
        /* defaults */
    };
    options = $.extend({}, defaults, options);

    var mapOptions = {
        center: new google.maps.LatLng(options.latitude,options.longitude),
        zoom: 12,
        mapTypeId: google.maps.MapTypeId.ROADMAP
    },
        map = new google.maps.Map(this[0], mapOptions),
        pos = new google.maps.LatLng(options.latitude,options.longitude);

    var marker = new google.maps.Marker({
        position: pos,
        title: options.title,
        icon: options.icon
    });

    marker.setMap(map);

    options.feed.update(function(latitude, longitude) {
        marker.setMap(null);
        var newLatLng = new google.maps.LatLng(latitude, longitude);
        marker.position = newLatLng;
        marker.setMap(map);
        map.setCenter(newLatLng);
    });

    return this;
};

var updater = (function() {    
    // private properties

    return {
        update: function(callback) {
            updateMap = callback;
        }
    };
})();

$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater
});

En esta fragmento, tenemos una pequeña biblioteca que convierte un determinado div en un mapa mostrando los datos de la ubicación actual, obtenidos estos de un elemento que se está observando. La función trackMap tiene dos dependencias: el API de terceros de Google Maps y un feed de la ubicación. La responsabilidad del objeto feed es simplemente invocar un callback (suministrada durante el proceso de inicialización) con unas nuevas latitud y longitud cuando la ubicación del icono se actualiza. La API de Google Maps se utiliza para hacer el render del mapa de la pantalla.

Mientras que la interfaz del objeto feed puede o no haber sido diseñada en relación a la función trackMap, el hecho de que su funcionalidad sea simple y concisa hace que sea fácil de sustituir por diferentes implementaciones. No es así con la dependencia de Google Maps. Dado que la función trackMap está semánticamente acoplada a la API de Google Maps, cambiar a un proveedor de mapas diferente requeriría que la función trackMap fuese reescrita o que escribiéramos un adaptador que adaptase otro proveedor de mapas a la interfaz específica de Google.

Para invertir el acoplamiento semántico de la biblioteca de Google Maps, se tiene que rediseñar la función trackMap para que tenga un acoplamiento semántico a una interfaz implícita abstracta que represente la funcionalidad necesaria por un proveedor de mapas. Entonces tenemos que implementar un objeto que se adapte a la interfaz del API de Google Maps. A continuación se muestra esta versión alternativa de la función trackMap:

$.fn.trackMap = function(options) {
    var defaults = { 
        /* defaults */
    };

    options = $.extend({}, defaults, options);

    options.provider.showMap(
        this[0],
        options.latitude,
        options.longitude,
        options.icon,
        options.title);

    options.feed.update(function(latitude, longitude) {
        options.provider.updateMap(latitude, longitude);
    });

    return this;
};


$("#map_canvas").trackMap({
    latitude: 35.044640193770725,
    longitude: -89.98193264007568,
    icon: 'http://bit.ly/zjnGDe',
    title: 'Tracking Number: 12345',
    feed: updater,
    provider: trackMap.googleMapsProvider
});

En esta versión, hemos rediseñado la función trackMap para expresar sus necesidades en relación a una interfaz genérica de proveedores de mapas proveedor y hemos trasladado los detalles de implementación hacia un componente googleMapsProvider separado que puede ser incluido como un módulo separado JavaScript. Aquí está nuestra aplicación googleMapsProvider:

trackMap.googleMapsProvider = (function() {
    var marker, map;

    return {
        showMap: function(element, latitude, longitude, icon, title) {
            var mapOptions = {
                center: new google.maps.LatLng(latitude, longitude),
                zoom: 12,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            },
                pos = new google.maps.LatLng(latitude, longitude);

            map = new google.maps.Map(element, mapOptions);

            marker = new google.maps.Marker({
                position: pos,
                title: title,
                icon: icon
            });

            marker.setMap(map);
        },
        updateMap: function(latitude, longitude) {
            marker.setMap(null);
            var newLatLng = new google.maps.LatLng(latitude,longitude);
            marker.position = newLatLng;
            marker.setMap(map);
            map.setCenter(newLatLng);
        }
    };
})();

Con estos cambios, nuestra función trackMap es ahora más resistente a los cambios que puedan producirse en la API de Google Maps y es susceptible de ser reutilizado con otro proveedor de mapas. Por supuesto, siempre y cuando la API se pueda adaptar a las necesidades de nuestra aplicación.

¿Adónde la inyección de dependencias?


Aunque no está totalmente relacionado, el concepto de inyección de dependencias se confunde a menudo con el principio de inversión de dependencias debido a la similitud en la terminología. Por esta razón, una discusión de las diferencias entre los dos conceptos puede resultar útil para algunos.

La inyección de dependencias es una forma específica de inversión de control en donde la preocupación de como un componente obtiene sus dependencias está invertida. Cuando se utiliza la inyección de dependencias, las dependencias se suministran al componente en vez de que sea este el que la obtenga mediante la creación de una instancia de dicha dependencia, solicitando la dependencia a través de un Factory, de un Service Locator, o mediante cualquier otro medio de iniciación del componente en sí. Tanto el principio de inversión de dependencias como la inyección de dependencias tienen que ver con las dependencias y ambos utilizan la noción de inversión para contrastar un enfoque alternativo con el presuntamente enfoque estándar. Sin embargo, el principio de inversión de dependencias no se ocupa de cómo los componentes obtienen sus dependencias, sino del desacoplamiento de los componentes de alto nivel con respecto a los componentes de bajo nivel. En cierto sentido, el principio de inversión de dependencias podría decirse que es otra forma de inversión de control donde la preocupación del módulo que define la interfaz está invertida.

Conclusión


Esto nos lleva al final de nuestra serie. En el transcurso de nuestro examen, a la par que hemos visto variaciones en como los principios de diseño SOLID aplican a JavaScript con respecto a otros lenguajes, también hemos visto como cada uno de estos principios han demostrado tener un cierto grado de aplicabilidad en el desarrollo de JavaScript.
Comparte:    Facebook Twitter
Leer más

19 de septiembre de 2013

Principios SOLID con JavaScript: El principio de segregación de interfaz (Traducción del Inglés)


En mi afán de aprender bien JavaScript, y ya de paso un poquito de inglés y un poquito de Ingeniería del Software, me he propuesto traducir una serie de artículos sobre los principios SOLID aplicados a JavaScript. El cuarto de ellos es: SOLID JavaScript: The Interface Segregation Principle.


Esta es la cuarta entrega de la serie "SOLID JavaScript" que explora los principios de diseño SOLID dentro del contexto del lenguaje JavaScript. En esta entrega, vamos a explicar el principio de segregación de la interfaz.

El principio de segregación de la interfaz


El principio de segregación de la interfaz se refiere a la cohesión de las interfaces dentro de un sistema. El principio establece:
Los clientes no deben ser forzados a depender de los métodos que no utilizan.
Cuando los clientes dependen de objetos que contienen métodos utilizados solamente por otros clientes, o se ven forzados a implementar métodos no utilizados con funcionalidad degradada (que puede conducir a una incumplimiento del principio de substitución de Liskov), esto puede producir código frágil. Esto ocurre cuando un objeto se utiliza como implementación de una interfaz poco cohesionada.

El principio de segregación de la interfaz es similar al principio de responsabilidad única puesto que ambos se centran en la cohesión de responsabilidades. De hecho, el ISP (Interface Segregation Principle) puede ser entendido como la aplicación del SRP (Single Responsibility Principle) para la interfaz pública de un objeto.

Interfaces de JavaScript


Así que, ¿qué tiene que ver este principio con JavaScript? Después de todo, JavaScript no tiene interfaces, ¿verdad? Si por interfaces nos referimos a algún tipo abstracto proporcionado por el lenguaje para establecer contratos y permitir desacoplamiento, entonces, tal afirmación sería correcta. Sin embargo, JavaScript tiene otro tipo de interfaz. En el libro Design Patterns: Elements of Reusable Object-Oriented Software por Gamma y otro, nos encontramos con la siguiente definición de una interfaz:
Todas las operaciones declaradas por un objeto se definen por su nombre, los objetos que cogen como parámetros y su valor de retorno. Esto es conocido como firma de la operación. El conjunto de todas las firmas definidas por las operaciones de un objeto se le denomina interfaz del objeto. La interfaz de un objeto engloba el conjunto de solicitudes que se le pueden enviar al objeto.
Independientemente de si un lenguaje proporciona una construcción separada para la representación de las interfaces o no, todos los objetos tienen una interfaz implícita compuesta por el conjunto de propiedades y métodos públicos del objeto. Por ejemplo, considere la siguiente biblioteca:

var exampleBinder = {};
exampleBinder.modelObserver = (function() {
    /* private variables */
    return {
        observe: function(model) {
            /* code */
            return newModel;
        },
        onChange: function(callback) {
            /* code */
        }
    }
})();

exampleBinder.viewAdaptor = (function() {
    /* private variables */
    return {
        bind: function(model) {
            /* code */
        }
    }
})();

exampleBinder.bind = function(model) {
    /* private variables */
    exampleBinder.modelObserver.onChange(/* callback */);
    var om = exampleBinder.modelObserver.observe(model);
    exampleBinder.viewAdaptor.bind(om);
    return om;
};

Esta ejemplo muestra una biblioteca denominada exampleBinder cuyo propósito es facilitar una doble vía de enlace de datos (data-binding). La interfaz pública de la biblioteca está representada por el método bind. Las responsabilidades de notificación de cambio y de interacción con la vista se han separado en los objetos modelObserver y viewAdaptor respectivamente para permitir implementaciones alternativas. Estos objetos representan las implementaciones de las interfaces esperadas por el método bind. Cualquier objeto que se adhiera a la semántica de comportamiento representada por estas interfaces puede ser sustituido por las implementaciones por defecto.

Aunque JavaScript como lenguaje no proporcione interfaces para ayudar en la especificación del contrato de un objeto, la interfaz implícita del objeto todavía sirve como contrato para el código cliente dentro de una aplicación.

ISP y JavaScript


Las siguientes secciones analizan algunas de las consecuencias de incumplir el principio de segregación de la interfaz en JavaScript. Como verás, a la vez que el ISP es aplicable al diseño de aplicaciones JavaScript, las características del lenguaje ofrecen un poco más de resistencia a las interfaces no cohesivas que en los lenguajes de tipado estático.

Degenerado Implementaciones


En los lenguajes de tipado estático, una cuestión que surge al incumplir el ISP es la necesidad de crear implementaciones degradadas. En lenguajes como Java y C#, todos los métodos declarados en una interfaz deben de ser implementados. Para los casos en los que se requiere una interfaz en particular, pero sólo un subconjunto de la conducta es relevante para un escenario de uso dado, los métodos no utilizados generalmente se rellenan con implementaciones vacías o con implementaciones que simplemente lanzan una excepción que indica que el método realmente no está implementado . En JavaScript, los casos en los que se utiliza sólo un subconjunto de la interfaz de un objeto no terminan planteando las mismas cuestiones puesto que un objeto sustituto sólo necesita ofrecer las propiedades esperadas para ajustarse a la parte consumida de la interfaz del objeto. Sin embargo, estas implementaciones pueden todavía incumplir el principio de sustitución de Liskov.

Por ejemplo, considere el siguiente ejemplo:


var rectangle = {
    area: function() { 
        /* code */
    },
    draw: function() { 
        /* code */
    }
};

var geometryApplication = {
    getLargestRectangle: function(rectangles) { 
        /* code */
    }
};

var drawingApplication = {
    drawRectangles: function(rectangles) {
       /* code */
    }
};

Mientras que un rectángulo sustituto podría ser creado con un sólo método area() con el fin de satisfacer las necesidades de método getLargestRectangle de geometryApplication, tal implementación representaría un incumplimiento LSP con respecto al método drawRectangles de drawingApplication.

Acoplamiento estático


Otra cuestión que se plantea en lenguajes de tipado estático es el tema del acoplamiento estático. En lenguajes de tipado estático, las interfaces juegan un papel importante en el diseño de aplicaciones de acoplamiento débil. Tanto en lenguajes estáticos como dinámicos, hay momentos en los que un objeto puede necesitar colaborar con varios clientes (por ejemplo, en los casos en donde puede ser necesario compartir el estado). Para lenguajes de tipado estático, se considera una buena práctica que los clientes interactúen con objetos que utilizan Rol Interfaces. Esto permite a los clientes interactuar con un objeto que puede requerir la intersección de múltiples funciones como detalle de implementación sin acoplar al cliente a un comportamiento no utilizado. En JavaScript, esto no es un problema ya que los objetos están desacoplados en virtud de las capacidades dinámicas del lenguaje.

Acoplamiento semántico


Una consecuencia que es igualmente relevante en JavaScript y en lenguajes de tipado estático es el tema del acoplamiento semántico. El acoplamiento semántico es la interdependencia que se produce cuando una parte del comportamiento de un objeto está acoplado con otra. Cuando la interfaz de un objeto especifica responsabilidades no cohesivos, los cambios para adaptarse a las cambiantes necesidades de un cliente pueden afectar inadvertidamente a otro cliente como resultado de los cambios realizados en el estado o comportamiento compartido. Esta es también una de las posibles consecuencias de incumplir el principio de responsabilidad única, aunque esto suele ser visto desde la perspectiva del impacto que tiene el comportamiento no cohesivo sobre objetos que colaboran entre sí. Desde la perspectiva del ISP, esta consecuencia se extiende a través de la herencia o de la sustitución del objeto.

Extensibilidad


Otro papel importante que las interfaces desempeñan en aplicaciones JavaScript se encuentra en la zona de extensibilidad. El ejemplo más común de esto se puede ver en el uso de callbacks donde una función conforme a la interfaz esperada se pasa a una biblioteca que se invoca en algún momento en el futuro (por ejemplo, en una petición exitosa de AJAX). El principio de segregación de la interfaz se vuelve relevante cuando dichas interfaces requieren la implementación de un objeto con múltiples propiedades y/o métodos. Cuando una interfaz comienza a requerir la implementación de un número significativo de métodos, esto hace el trabajo con la biblioteca utilizada más difícil y es probablemente una indicación de que las interfaces representan responsabilidades no cohesivas. Esto es a menudo descrito como interfaces "fat".

En resumen, las capacidades dinámicas de JavaScript permiten que la aparición de interfaces no cohesivas tengan menos consecuencias que en lenguajes de tipado estático, pero no obstante el principio de segregación de la interfaz tiene su lugar en el diseño de aplicaciones de JavaScript.

La próxima vez, vamos a discutir el siguiente principio en el acrónimo SOLID: el principo de inversión de dependencias.
Comparte:    Facebook Twitter
Leer más

5 de septiembre de 2013

Principios SOLID con JavaScript: El principio de sustitución de Liskov (Traducción del Inglés)


En mi afán de aprender bien JavaScript, y ya de paso un poquito de inglés y un poquito de Ingeniería del Software, me he propuesto traducir una serie de artículos sobre los principios SOLID aplicados a JavaScript. El tercero de ellos es: SOLID JavaScript: The Liskov Substitution Principle.


Esta es la tercera entrega de la serie "SOLID JavaScript" que explora los principios de diseño SOLID dentro del contexto del lenguaje JavaScript. En esta entrega, vamos a explicar el principio de sustitución de Liskov.

El principio de sustitución Liskov


El principio de sustitución Liskov se refiere a la interoperabilidad de los objetos dentro de una jerarquía de herencia. El principio declara:
Los subtipos deben ser sustituibles por sus tipos base.
En la programación orientada a objetos, la herencia proporciona un mecanismo para el intercambio de código dentro de una jerarquía de tipos relacionados. Esto se logra mediante un proceso de encapsulación de los datos y comportamientos comunes dentro de un tipo base, y luego definir tipos especializados en función del tipo base. Para cumplir con el principio de sustitución Liskov, un tipo derivado debe ser semánticamente equivalente a la conducta esperada de su tipo base.

Para ilustrar, considere el siguiente código:

function Vehicle(my) {

    my = my || {};
    my.speed = 0;
    my.running = false;

    this.speed = function() {
        return my.speed;
    };

    this.start = function() {
        my.running = true;
    };

    this.stop = function() {
        my.running = false;
    };

    this.accelerate = function() {
        my.speed++;
    };

    this.decelerate = function() {
        my.speed--;
    };

    this.state = function() {
        if (!my.running) {
            return "parked";
        }
        else if (my.running && my.speed) {
            return "moving";
        }
        else if (my.running) {
            return "idle";
        }
    };
}

Este listado muestra el constructor Vehicle que proporciona unas operaciones básicas para un objeto de tipo vehículo. Digamos que este constructor se está utilizando actualmente en producción por varios clientes. Ahora bien, consideramos que se nos pide añadir un nuevo constructor que represente a los vehículos de movimiento rápido. Después de pensarlo un poco, nos encontramos con el siguiente nuevo constructor:

function FastVehicle(my) {

    my = my || {};
   
    var that = new Vehicle(my);

    that.accelerate = function() {
        my.speed += 3;
    };

    return that;
}

Después de probar nuestro nuevo constructor FastVehicle en la ventana de la consola del navegador, estamos satisfechos de que todo funciona como se esperaba. Los objetos creados con FastVehicle aceleran 3 veces más rápido que antes y todos los métodos heredados funcionan según lo previsto. Seguros de que todo funciona como se espera, decidimos desplegar la nueva versión de la biblioteca. Sin embargo, al utilizar el nuevo tipo nos informan de que los objetos creados con el constructor FastVehicle rompen el código cliente existente. Aquí está el fragmento de código que muestra el problema:

var maneuver = function(vehicle) {

    write(vehicle.state());
    vehicle.start();
    write(vehicle.state());
    vehicle.accelerate();
    write(vehicle.state());
    write(vehicle.speed());
    vehicle.decelerate();
    write(vehicle.speed());
    if (vehicle.state() != "idle") {
        throw "The vehicle is still moving!";
    }
    vehicle.stop();
    write(vehicle.state());
};

Al ejecutar el código, vemos que se produce una excepción: "El vehículo está en movimiento". Parece ser que el autor de esta función hace la suposición de que los vehículos siempre aceleran y desaceleran uniformemente. Los objetos creados a partir de nuestro FastVehicle no son completamente intercambiables por los objetos creados a partir de nuestro constructor del vehículo base. Nuestro FastVehicle incumple el principio de sustitución de Liskov!

En este punto, usted puede estar pensando: "Pero, el cliente no debería haber asumido que todos los vehículos se comportan de esa manera." Irrelevante! Los incumplimientos del principio de sustitución de Liskov no se basan en si pensamos que los objetos derivados deben ser capaces de realizar ciertas modificaciones en el comportamiento, sino en si tales modificaciones se pueden hacer a la luz de las expectativas actuales.

En el caso de este ejemplo, la resolución del problema de incompatibilidad requiere un poco de rediseño en la biblioteca de vehículo, en los clientes que la consumen, o quizás en ambos.

Atenuando los incumplimientos LSP


Así que, ¿cómo podemos interceptar los incumplimientos al principio de sustitución de Liskov? Desafortunadamente, esto no siempre es posible. Para evitar los incumplimientos LSP al completo, te enfrentas a la difícil tarea de anticipar todas las posibles formas de como la biblioteca puede ser utilizada. Añádase a esto la posibilidad de que puedes no ser el autor original del código que se está pidiendo extender y puede que no tengas visibilidad de todas las formas de como se está utilizando actualmente la biblioteca. Dicho esto, hay algunas estrategias para evitar los incumplimientos dentro de su propio código.

Contratos


Una estrategia para interceptar los incumplimientos más graves del LSP es utilizar contratos. Los contratos se manifiestan de dos formas principales: especificaciones ejecutables y comprobación de errores. Con especificaciones ejecutables, el contrato de como una biblioteca particular está destinada a ser usada está contenido en un conjunto de pruebas automatizadas. El segundo enfoque consiste en incluir la comprobación de errores directamente en el código en forma de precondiciones, postcondiciones y chequeos. Esta técnica se conoce como Diseño Por Contrato, un término acuñado por Bertrand Meyer, creador del lenguaje de programación de Eiffel. Los temas sobre pruebas automatizadas y sobre el Diseño Por Contrato están más allá del alcance de esta serie, pero os dejamos las siguientes recomendaciones para cuándo usar cada uno.

  • Utilizar siempre el Desarrollo Guiado por Pruebas (TDD - Test Driven Development) para guiar el diseño de su propio código.
  • Opcionalmente utilizar técnicas de Diseño Por Contrato en el diseño de bibliotecas reutilizables.
Para el código que vas a mantener y que consumes tú mismo, el uso de técnicas de Diseño Por Contrato tienden simplemente a añadir un montón de ruido innecesario y aumentar los gastos generales de su código. Si tienes el control de los datos de entrada, un conjunto de pruebas sirve como un mejor control para especificar como una biblioteca está destinada a ser usada. Si eres el autor de bibliotecas reutilizables, el Diseño Por Contrato sirve tanto para protegerse contra el uso indebido y como herramienta de depuración para sus usuarios.

Evite la herencia


Otra estrategia para evitar incumplimientos LSP es reducir al mínimo el uso de la herencia cuando sea posible. En el libro Design Patterns: Elements of Reusable Object-Oriented Software por Gamma y otros, nos encontramos con los siguientes consejos:
Composición de los objetos mejor que herencia de clases
Mientras que algunos de los debates del libro sobre las ventajas de la composición sobre la herencia es relevante sólo para lenguajes de tipado estático, basados ​​en la clase (por ejemplo, la imposibilidad de cambiar el comportamiento en tiempo de ejecución), para JavaScript uno de los temas relevantes es el del acoplamiento. Al utilizar la herencia, los tipos derivados están acoplado a sus tipos base. Esto significa que los cambios producidos en el tipo base pueden afectar sin darnos cuenta a los tipos derivados. Además, la composición tiende a producir objetos más pequeños y más específicos que son más fáciles de mantener tanto para lenguajes dinámicos como estáticos.

Es sobre comportamiento, no sobre herencia


Hasta ahora, hemos discutido el principio de sustitución de Liskov en el contexto de la herencia, lo cual es perfectamente apropiado dado que JavaScript es un lenguaje orientado a objetos. Sin embargo, la esencia del principio de sustitución de Liskov no está realmente preocupado por la herencia, sino por la compatibilidad en el comportamiento. JavaScript es un lenguaje dinámico, por lo tanto, el contrato del comportamiento de un objeto no está determinado por el tipo del objeto, sino por las capacidades esperadas por dicho objeto. Mientras que el principio de sustitución de Liskov fue concebido originalmente como un principio guiado por la herencia, es igualmente relevante para el diseño de objetos que se adhieren a interfaces implícitas.

Para ilustrar esto, consideremos un ejemplo tomado del libro Agile Software Development, Principles, Patterns, and Practices de Robert C. Martin: el ejemplo del rectángulo.

El ejemplo del rectángulo


Consideremos que tenemos una aplicación que utiliza un objeto rectángulo definido de la siguiente manera:

var rectangle = {
    length: 0,
    width: 0
};

Posteriormente, se determina que la aplicación también tiene que trabajar con un cuadrado. Basándose en el conocimiento de que un cuadrado es un rectángulo cuyos lados son iguales en longitud, decidimos crear un objeto cuadrado en lugar de usar el objeto rectángulo. Añadimos las propiedades de longitud y anchura para que coincida con la definición del objeto rectángulo, pero decidimos usar getters y setters para dichas propiedades de tal forma que podamos mantener los valores de longitud y anchura sincronizados, garantizando que se adhiera a la definición de un cuadrado:

var square = {};
(function() {
    var length = 0, width = 0;
    Object.defineProperty(square, "length", {
        get: function() { return length; },
        set: function(value) { length = width = value; }
    });
    Object.defineProperty(square, "width", {
        get: function() { return width; },
        set: function(value) { length = width = value; }
    });
})();

Desafortunadamente, un problema se descubre cuando la aplicación intenta utilizar nuestro cuadrado en lugar del rectángulo. Resulta que uno de los métodos calcula el área del rectángulo así:

var g = function(rectangle) {
    rectangle.length = 3;
    rectangle.width = 4;
    write(rectangle.length);
    write(rectangle.width);
    write(rectangle.length * rectangle.width);
};

Cuando el método se invoca con el cuadrado, el producto es 16 más que el valor esperado de 12. Nuestro objeto cuadrado incumple el principio de sustitución Liskov con respecto a la función g. En este caso, la presencia de las propiedades de longitud y anchura era un indicio de que nuestro cuadrado podría no llegar a ser 100% compatible con el rectángulo, pero no siempre existirán estos indicios evidentes. La corrección de esta situación probablemente requerirá de un rediseño de los objetos de forma y de la aplicación que los utiliza. Un enfoque más flexible podría ser la definición de rectángulos y cuadrados en términos de polígonos. En cualquier caso, lo importante a relucir de este ejemplo es que el principio de sustitución Liskov no es solamente relevante con la herencia, sino también con cualquier planteamiento en donde un comportamiento sea sustituido por otro.

La próxima vez, vamos a discutir el siguiente principio en el acrónimo SOLID: el princio de segregación de iterfaz.
Comparte:    Facebook Twitter
Leer más

3 de agosto de 2013

Principios SOLID con JavaScript: El principio abierto/cerrado (Traducción del Inglés)


En mi afán de aprender bien JavaScript, y ya de paso un poquito de inglés y un poquito de Ingeniería del Software, me he propuesto traducir una serie de artículos sobre los principios SOLID aplicados a JavaScript. El segundo de ellos es: SOLID JavaScript: The Open/Closed Principle.


Esta es la segunda entrega de la serie "SOLID JavaScript" que explora los principios de diseño SOLID dentro del contexto del lenguaje JavaScript. En esta entrega, vamos a explicar el principio abierto/cerrado.

El principio abierto/cerrado


El principio abierto/cerrado se refiere a la extensión de los objetos. El principio declara:
Las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para su extensión, pero cerradas para su modificación
Con "abiertas para su expansión", en este principio se entiende como la capacidad de una entidad para adaptarse a las necesidades cambiantes de una aplicación. Con "cerradas para su modificación", este principio quiere decir que la adaptación de una entidad no debe hacerse por la modificación de la entidad original. De una forma más simple, las entidades que realizan comportamientos variados deben de estar diseñadas para facilitar los cambios, sin necesidad de modificación. La adhesión al principio abierto/cerrado puede ayudar a mejorar la mantenibilidad, reduciendo al mínimo los cambios realizados en nuestro código.

Para ayudar a ilustrar, vamos a echar un vistazo al siguiente ejemplo que convierte de forma dinámica preguntas a una pantalla en función del tipo de respuesta esperada para cada pregunta:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Open/Closed Principle: 
      Example 1 - jsFiddle demo by derekgreer</title>
  <script type='text/javascript'
      src='http://code.jquery.com/jquery-1.7.1.js'></script>
  <script type='text/javascript'>//<![CDATA[ 
  $(window).load(function(){
      var AnswerType = {
          Choice: 0,
          Input: 1
      };

      function question(label, answerType, choices) {
          return {
              label: label,
              answerType: answerType,
              choices: choices
          };
      }

      var view = (function() {
          function renderQuestion(target, question) {
              var questionWrapper = document.createElement('div');
              questionWrapper.className = 'question';

              var questionLabel = document.createElement('div');
              questionLabel.className = 'question-label';
              var label = document.createTextNode(question.label);
              questionLabel.appendChild(label);

              var answer = document.createElement('div');
              answer.className = 'question-input';

              if (question.answerType === AnswerType.Choice) {
                  var input = document.createElement('select');
                  var len = question.choices.length;
                  for (var i = 0; i < len; i++) {
                      var option = document.createElement('option');
                      option.text = question.choices[i];
                      option.value = question.choices[i];
                      input.appendChild(option);
                  }
              }
              else if (question.answerType === AnswerType.Input) {
                  var input = document.createElement('input');
                  input.type = 'text';
              }

              answer.appendChild(input);
              questionWrapper.appendChild(questionLabel);
              questionWrapper.appendChild(answer);
              target.appendChild(questionWrapper);
          }

          return {
              render: function(target, questions) {
                  for (var i = 0; i < questions.length; i++) {
                      renderQuestion(target, questions[i]);
                  };
              }
          };
      })();

      var questions = [
          question(
              'Have you used tobacco within the last 30 days?', 
              AnswerType.Choice, ['Yes', 'No']),
          question(
              'What medications are you currently using?', 
              AnswerType.Input)];

      var questionRegion = document.getElementById('questions');
      view.render(questionRegion, questions);
  });//]]>
  </script>
  <style type='text/css'>
    .question {
        clear: both;
        padding: 0 0 30px 0; 
    }
    .question-label {
        float: left;
        width: 240px;
        padding: 0 4px 0 0;
    }
  </style>
</head>
<body>
  <div id='questions'></div>
</body>
</html>

En este ejemplo, un objeto de vista contiene un método de procesamiento que hace preguntas basadas en cada tipo de pregunta recibida. Una cuestión consiste en una etiqueta, un tipo de respuesta (opción o entrada de texto), y una lista opcional de opciones. Si el tipo de respuesta es Answer.Choice, un menú desplegable se crea con las opciones disponibles. Si el tipo de respuesta es AnswerType.Input, una entrada de texto simple se representa.

Siguiendo el patrón ya establecido, para añadir nuevos tipos de entrada se requiere agregar nuevas condiciones en el método render. Esto violaría el principio abierto/cerrado.

Echemos un vistazo a una implementación alternativa que nos permita ampliar las capacidades de la vista de renderizado de objetos sin requerir cambios en el objeto vista para cada tipo de respuesta nuevo:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Open/Closed Principle: 
      Example 2 - jsFiddle demo by derekgreer</title>
  <script type='text/javascript' 
      src='http://code.jquery.com/jquery-1.7.1.js'></script>
  <script type='text/javascript'>//<![CDATA[ 
  $(window).load(function(){
      function questionCreator(spec, my) {
          var that = {};

          my = my || {};
          my.label = spec.label;

          my.renderInput = function() {
              throw "not implemented";
          };

          that.render = function(target) {
              var questionWrapper = document.createElement('div');
              questionWrapper.className = 'question';

              var questionLabel = document.createElement('div');
              questionLabel.className = 'question-label';
              var label = document.createTextNode(spec.label);
              questionLabel.appendChild(label);

              var answer = my.renderInput();

              questionWrapper.appendChild(questionLabel);
              questionWrapper.appendChild(answer);
              return questionWrapper;
          };

          return that;
      }

      function choiceQuestionCreator(spec) {

          var my = {},
              that = questionCreator(spec, my);

          my.renderInput = function() {
              var input = document.createElement('select');
              var len = spec.choices.length;
              for (var i = 0; i < len; i++) {
                  var option = document.createElement('option');
                  option.text = spec.choices[i];
                  option.value = spec.choices[i];
                  input.appendChild(option);
              }

              return input;
          };

          return that;
      }

      function inputQuestionCreator(spec) {

          var my = {},
              that = questionCreator(spec, my);

          my.renderInput = function() {
              var input = document.createElement('input');
              input.type = 'text';
              return input;
          };

          return that;
      }

      var view = {
          render: function(target, questions) {
              for (var i = 0; i < questions.length; i++) {
                  target.appendChild(questions[i].render());
              }
          }
      };

      var questions = [
          choiceQuestionCreator({
            label: 'Have you used tobacco within the last 30 days?',
            choices: ['Yes', 'No']
          }),
          inputQuestionCreator({
            label: 'What medications are you currently using?'
          })];

      var questionRegion = document.getElementById('questions');

      view.render(questionRegion, questions);
  });//]]>  
  </script>
  <style type='text/css'>
    .question {
        clear: both;
        padding: 0 0 30px 0; 
    }
    .question-label {
        float: left;
        width: 240px;
        padding: 0 4px 0 0;
    }
  </style>
</head>
<body>
  <div id='questions'></div>
</body>
</html>

Hay algunas técnicas que se utilizan aquí, así que vamos a ir viéndolas una a una.

En primer lugar, hemos refactorizado el código responsable de crear preguntas con respuesta en un constructor funcional llamado questionCreator. Este constructor utiliza el patrón Template Method para delegar la creación de cada respuesta a los tipos que queramos extender.

En segundo lugar, hemos reemplazado el uso de las propiedades del constructor anterior por una propiedad privada de especificaciones que sirve como interfaz al constructor questionCreator. Ya que estamos encapsulando el comportamiento de representación de los datos con los que opera, ya no necesitamos que estas propiedades sean de acceso público.

En tercer lugar, hemos identificado el código que crea cada tipo de respuesta como una familia de algoritmos y hemos refactorizado cada algoritmo en un objeto separado (una técnica conocida como el patrón Strategy) que extiende del objeto questionCreator utilizando herencia diferencial.

Como beneficio adicional a esta refactorización, hemos sido capaces de eliminar la necesidad de la enumeración AnswerType y pudimos extraer el array de decisiones como requisito específico de la interfaz choiceQuestionCreator.

La versión refactorizada del objeto vista puede ahora extenderse limpiamente simplemente extendiendo nuevos objetos questionCreator.

La próxima vez, vamos a discutir el tercer principio del acrónico SOLID: el principio de sustitución de Liskov.
Comparte:    Facebook Twitter
Leer más

28 de febrero de 2013

Principios SOLID con JavaScript: El principio de responsabilidad única (Traducción del Inglés)


En mi afán de aprender bien JavaScript, y ya de paso un poquito de inglés y un poquito de Ingeniería del Software, me he propuesto traducir una serie de artículos sobre los principios SOLID aplicados a al JavaScript. El primero de ellos es: SOLID JavaScript: The Single Responsibility Principle.


Esta es la primera entrega de la serie "SOLID JavaScript" que explorará los principios de diseño SOLID dentro del contexto del lenguaje JavaScript. En esta primera entrega, vamos a echar un vistazo a los principios de diseño SOLID y discutiremos el primero de ellos: el principio de responsabilidad única.

Los principios de diseño SOLID y JavaScript


SOLID es un acrónimo nemotécnico que se refiere a un conjunto de principios de diseño que se desarrolló entorno a la comunidad de programadores de lenguajes orientados a objetos y que fueron popularizados por los escritos de Robert C. Martin. Estos principios son los siguientes:
  • El principio de responsabilidad única
  • El principio abierto/cerrado
  • El principio de sustitución Liskov
  • El principio de segregación de iterfaz
  • El principio de inversión de dependencias
Estos principios se discuten a menudo en el contexto de lenguajes clásicos, de tipado estático, y orientados a objetos, y aunque JavaScript es un lenguaje basado en prototipos y de tipado dinámico, tiene mezcla de ambos paradigmas, el orientado a objetos y el funcional, por lo que los programadores aún puede cosechar los beneficios de la aplicación de estos principios a JavaScript. Este artículo cubre el primero de estos principios: el principio de responsabilidad única.

El principio de responsabilidad única


El principio de responsabilidad única se refiere a la relación funcional de los elementos de un módulo. El principio declara:
Una clase debe tener sólo una razón para cambiar
Esta descripción puede ser un poco engañosa, ya que parece indicar que un objeto sólo debe hacer una cosa. Sin embargo, lo que quiere decir esta afirmación es que un objeto debe tener un conjunto coherente de comportamientos entorno a una única responsabilidad de tal forma que si esta responsabilidad cambia implica cambiar la definición del objeto. De una forma más simple, la definición de un objeto sólo debería tener que ser modificada debido a cambios en una única responsabilidad dentro del sistema.

La adhesión al principio de responsabilidad única ayuda a mejorar la mantenibilidad limitando las responsabilidades de un objeto a únicamente aquellas que cambien por razones relacionadas. Cuando un objeto encapsula múltiples responsabilidades, los cambios en una de las responsabilidades del objeto puede afectar negativamente a las otras. Al desacoplar esas responsabilidades, podemos crear código que es más resistente al cambio.

Pero, ¿cómo identificar si un determinado conjunto de comportamientos constituye una responsabilidad única? ¿Agrupar la manipulación de strings en un único objeto es una responsabilidad única? ¿Y agrupar todas las llamadas a servicios de una aplicación? Sin un enfoque establecido para hacer estas determinaciones, la adhesión al principio de responsabilidad única puede ser un poco desconcertante.

Los roles estereotipados de objetos


Un enfoque que puede ayudar en la organización de las funciones dentro de un sistema es el uso de los roles estereotipados de objetos. Los roles estereotipados de objetos son un conjunto de roles generales y preestablecidos que comúnmente aparecen en arquitecturas orientadas a objetos. Mediante el uso de un conjunto de roles estereotipados, los desarrolladores pueden dotarse ellos mismos de un conjunto de plantillas que pueden utilizar a medida que avanzan a través del ejercicio mental de descomponer las funciones en componentes coherentes.

El concepto de los roles estereotipados de objetos se trata en el libro Object Design: Roles, Responsibilies, and Collaborations de Rebecca Wirfs-Brock y McKean Alan. El libro presenta los siguientes roles estereotipados:

  • Propietario de la información - un objeto diseñado para conocer determinada información y proporcionar esa información a los otros objetos.
  • Estructurador - un objeto que mantiene las relaciones entre los objetos y la información sobre esas relaciones.
  • Proveedor de servicios - un objeto que realiza un trabajo específico y ofrece servicios a otros bajo demanda.
  • Controlador - un objeto diseñado para tomar decisiones y controlar una tarea compleja.
  • Coordinador - un objeto que no toma muchas decisiones, pero, de un modo memorístico o mecánico, delega el trabajo en otros objetos.
  • Interfaz - un objeto que transforma la información o peticiones entre distintas partes de un sistema.
Aunque no es preceptivo, este conjunto de estereotipos proporciona un excelente marco mental para ayudar en el proceso de diseño de software. Una vez establecidos un conjunto de estereotipos para trabajar con ellos, veremos que es más fácil agrupar comportamientos en grupos de responsabilidades cohesionados relacionadas con el papel previsto para un objeto.

Ejemplo: El principio de responsabilidad única


Para ilustrar la aplicación del principio de responsabilidad única, consideremos el siguiente ejemplo donde se facilita el movimiento de unidades de producto hasta un carrito de la compra:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Single Responsibility Principle Example - 
    Version 1 - jsFiddle demo by derekgreer</title>
  <script type='text/javascript'
      src='http://code.jquery.com/jquery-1.7.1.js'></script>
  <script type='text/javascript'>//<![CDATA[ 
    $(window).load(function(){
    
        function Product(id, description) {
            this.getId = function() {
                return id;
            };
            this.getDescription = function() {
                return description;
            };
        }
    
        function Cart(eventAggregator) {
            var items = [];

            this.addItem = function(item) {
                items.push(item);
            };
        }
    
        var products = [
            new Product(1, "Star Wars Lego Ship"),
            new Product(2, "Barbie Doll"),
            new Product(3, "Remote Control Airplane")];
    
        var cart = new Cart();
    
        (function() {
            function addToCart() {
                var productId = $(this).attr('id');
                var product = $.grep(products, function(x) {
                    return x.getId() == productId;
                })[0];
                cart.addItem(product);

                var newItem = $('<li></li>')
                    .html(product.getDescription())
                    .attr('id-cart', product.getId())
                    .appendTo("#cart");
            }
            products.forEach(function(product) {
                var newItem = $('<li></li>')
                    .html(product.getDescription())
                    .attr('id', product.getId())
                    .dblclick(addToCart)
                    .appendTo("#products");
            });
        })();
    });//]]>  
  </script>
  <style type='text/css'>
    ul {
        border: 1px solid black;
        width: 200px;
        min-height: 100px;
    }
    .cart-wrapper {
        float: left;
        margin-left: 20px;
    }
   .products-wrapper {
        float: left;
    }
  </style>
</head>
<body>
  <div class="products-wrapper">
    <h2>Products</h2>
    <ul id="products"></ul>
  </div>
  <div class="cart-wrapper">
    <h2>Cart</h2>
    <ul id="cart"></ul>
  </div>
</body>
</html>

Aunque no es excesivamente complejo, este ejemplo ilustra una serie de responsabilidades no relacionadas que se agrupan dentro de una función anónima única. Vamos a considerar cada responsabilidad:

En primer lugar, tenemos un comportamiento definido para añadir un producto al objeto Cart cuando se hace doble clic en un elemento.

En segundo lugar, tenemos un comportamiento definido para añadir un producto a la vista del carrito cuando se hace doble clic en un elemento.

Tercero, tenemos un comportamiento definido para rellenar la vista de los productos con el conjunto inicial de productos.

Vamos a romper estas tres responsabilidades hacia sus propios objetos:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <title>Single Responsibility Principle Example - 
    Version 2 - jsFiddle demo by derekgreer</title>
  <script type='text/javascript' 
      src='http://code.jquery.com/jquery-1.7.1.js'></script>
    <script type='text/javascript'>//<![CDATA[ 
    $(window).load(function() {
        function Event(name) {
            this._handlers = [];
            this.name = name;
        }
        Event.prototype.addHandler = function(handler) {
            this._handlers.push(handler);
        };
        Event.prototype.removeHandler = function(handler) {
            for (var i = 0; i < handlers.length; i++) {
                if (this._handlers[i] == handler) {
                    this._handlers.splice(i, 1);
                    break;
                }
            }
        };
        Event.prototype.fire = function(eventArgs) {
            this._handlers.forEach(function(h) {
                h(eventArgs);
            });
        };

        var eventAggregator = (function() {
            var events = [];

            function getEvent(eventName) {
                return $.grep(events, function(event) {
                    return event.name === eventName;
                })[0];
            }

            return {
                publish: function(eventName, eventArgs) {
                    var event = getEvent(eventName);

                    if (!event) {
                        event = new Event(eventName);
                        events.push(event);
                    }
                    event.fire(eventArgs);
                },

                subscribe: function(eventName, handler) {
                    var event = getEvent(eventName);

                    if (!event) {
                        event = new Event(eventName);
                        events.push(event);
                    }

                    event.addHandler(handler);
                }
            };
        })();

        function Cart() {
            var items = [];

            this.addItem = function(item) {
                items.push(item);
                eventAggregator.publish("itemAdded", item);
            };
        }

        var cartView = (function() {
            eventAggregator.subscribe("itemAdded", function(eventArgs) {
                var newItem = $('<li></li>')
                    .html(eventArgs.getDescription())
                    .attr('id-cart', eventArgs.getId())
                    .appendTo("#cart");
            });
        })();

        var cartController = (function(cart) {
            eventAggregator.subscribe(
                "productSelected", 
                function(eventArgs) {
                    cart.addItem(eventArgs.product);
                });
        })(new Cart());

        function Product(id, description) {
            this.getId = function() {
                return id;
            };
            this.getDescription = function() {
                return description;
            };
        }

        var products = [
            new Product(1, "Star Wars Lego Ship"),
            new Product(2, "Barbie Doll"),
            new Product(3, "Remote Control Airplane")];

        var productView = (function() {
            function onProductSelected() {
                var productId = $(this).attr('id');
                var product = $.grep(products, function(x) {
                    return x.getId() == productId;
                })[0];
                eventAggregator.publish("productSelected", {
                    product: product
                });
            }

            products.forEach(function(product) {
                var newItem = $('<li></li>')
                    .html(product.getDescription())
                    .attr('id', product.getId())
                    .dblclick(onProductSelected)
                    .appendTo("#products");
            });
        })();
    });//]]>  
  </script>
  <style type='text/css'>
    ul {
        border: 1px solid black;
        width: 200px;
        min-height: 100px;
    }
    .cart-wrapper {
        float: left;
        margin-left: 20px;
    }
   .products-wrapper {
        float: left;
    }
  </style>
</head>
<body>
  <div class="products-wrapper">
    <h2>Products</h2>
    <ul id="products"></ul>
  </div>
  <div class="cart-wrapper">
    <h2>Cart</h2>
    <ul id="cart"></ul>
  </div>
</body>

En nuestro diseño revisado, hemos eliminado la función anónima y la hemos sustituido por objetos que coordinan cada uno de los conjuntos separados de responsabilidades. Se ha introducido el objeto cartView para coordinar los productos que se muestran en el carrito, se ha introducido el objeto cartController para coordinar los productos que se añaden al objeto Cart, y se ha introducido el objeto ProductView para coordinar los productos que se muestran en pantalla. También se ha introducido un agregador de eventos para facilitar la comunicación entre objetos mediante acoplamiento flexible. Aunque este diseño da como resultado un mayor número de objetos, cada objeto se centra ahora en el cumplimiento de una función específica dentro de la orquestación general con acoplamiento mínimo entre los objetos.

La próxima vez, vamos a discutir el siguiente principio en el acrónimo SOLID: el principio abierto / cerrado.
Comparte:    Facebook Twitter
Leer más