2

Patrones GRASP, un vistazo a través de CRIAX III (Bajo Acoplamiento)

21Sep
en CRIAX-SDK, Javascript, Tutoriales

Holas comunidad continuando con la serie de patrones GRASP, en el post anterior hablamos del patrón Creador, hoy les hablaré del patrón Bajo Acoplamiento.

Solución: asignar una responsabilidad para mantener un bajo acoplamiento.

Para saber que se necesita hacer u obtener:

Como disminuir la dependencia de una clase y aumentar su reutilización.

El acoplamiento es la medida con que una clase está conectada a otra. Una clase con bajo o débil acoplamiento no depende de muchas otras. Una clase con alto o fuerte acoplamiento recurre a muchas otras, ello conlleva a problemas como:

  • los cambios de las clases afines ocasionan cambios locales.
  • son más difíciles de entender cuando están aisladas.
  • son más difíciles de reutilizar porque se requiere la presencia de las otras clases de las que depende.

El grado de acoplamiento no debe considerarse aisladamente de otros principios como Experto y Alta Cohesión. Sin embargo es un factor a considerar cuando se intenta mejorar un diseño. Es un patrón evaluativo que el diseñador aplica al juzgar sus decisiones de diseño en la decisión de asignar responsabilidades.

Formas comunes de acoplamiento:

X posee un atributo que se refiere a una instancia de Y o al propio Y

X posee un atributo que se refiere a una instancia de Y o al propio Y

X tiene un método que a toda costa referencia una instancia de Y, o incluso al propio Y. Suele incluirse un parámetro o una variable local de tipo Y o bien el objeto devuelto de un método es una instancia de Y

X tiene un método que a toda costa referencia una instancia de Y, o incluso al propio Y. Suele incluirse un parámetro o una variable local de tipo Y o bien el objeto devuelto de un método es una instancia de Y

X es una subclase directa o indirecta de Y

X es una subclase directa o indirecta de Y

Y es una interfaz y X la implementa

Y es una interfaz y X la implementa

El bajo acoplamiento soporta el diseño de clases más independientes, esto reduce el impacto de los cambios, y a la vez las mismas son más reutilizables. No existe una medida absoluta de cuando el acoplamiento es excesivo. Lo importante es que el diseñador pueda determinar el grado actual de acoplamiento y si surgirán problemas en caso de incremento.

Han de tener bajo acoplamiento las clases muy genéricas y con grandes probabilidades de reutilización. Un grado de acoplamiento moderado entre clases es normal y necesario (POO).

Ejemplo

Se desea crear una instancia de Pago y asociarla a una Venta.

NOTA: TPDV (Terminal Punto de Venta).

Opción 1:

  • TPDV registra los pagos en el mundo real.
  • TPDV debería de encargarse de crear una instancia de Pago, según el patrón Creador.
  • TPDV también debería encargarse de crear una instancia de Venta y pasarle la instancia de Pago (ver si el pago puede pagar esa venta, ya que Venta tiene el saldo total).
Diagrama de alto acoplamiento (TPDV)

Diagrama de alto acoplamiento (TPDV)

Aquí la clase TPDV depende de 2 clases Pago y Venta.

Como mejorar este diseño.

Pregunta 1: Como disminuir la dependencia de la clase TPDV.

Opción 2:

Pudiera disminuirse si el pago se crea en la misma clase Venta.

Diagrama de bajo acoplamiento

Diagrama de bajo acoplamiento

Como se puede apreciar aquí se disminuye la dependencia de la clase TPDV por lo cual el acoplamiento es menor.

En el mundo real la segunda solución a pesar de ser la mejor opción en cuanto a acoplamiento, debería pensarse bien pues por ejemplo difiere de la forma de trabajar del patrón Creador. Si se tolera un poco el acoplamiento, sería la primera opción la utilizada.

Conclusiones

Utilizar el patrón Bajo Acoplamiento para mejorar los diseños siempre que sea posible.

Codificación Opción 1:

Para llevar a cabo la codificación comenzamos traduciendo el diagrama obtenido a código. Para ello partimos del código anterior del patrón Creador. Lo primero será crear la clase Pago, muy sencilla:

/**
  * Modelo para la clase pago
  * 
  * @class Pago
  * @public
  * @extends qx.core.Object
  * @author Nilmar S�nchez Muguercia
  * @namespace tienda.model  
  * @copyrigth 
  * @license
  * @version 1.4
  *
  */
 
qx.Class.define("tienda.model.Pago",
{
    extend : qx.core.Object,
 
    /**
     * @property
     */
    properties :
    {
        /**
         *propiedad para el monto
         * 
         * @name __monto
         * @private
         * @type {Float}
         * 
         */
        __monto : {}
    },
 
    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public     
     * @param monto {Float}: a pagar
     *
     */
 
    construct : function(monto)
    {
        this.base(arguments);        
        this.__monto = monto || 0.00;
    },
 
   /**
     * @method
     */
    members :
    {
        /**
         * metodo para devolver el monto
         *
         * @method getMonto
         * @public         
         * @return {Float} el monto
         * 
         */ 
 
        getMonto : function(){
            return this.__monto.toFixed(2);
        }
    }
});

Luego modificaremos la clase Venta, para agregarle un atributo de tipo Pago, con sus respectivos métodos accesor y mutador.



//agregando el atributo de pago
/**
 * propiedad para las ventas de productos
 * 
 * @name __pago
 * @private
 * @type {Array}
 * 
 */
__pago : {}
 
//metodos accesor y mutador de pago
/**
 *  metodo para agregar un pago
 *
 * @method setPago
 * @public
 * @param pago {Pago} el pago de la venta
 * @return {Venta}
 * 
 */ 
 
setPago : function(pago){
    this.__pago = pago;
    return this;
},
 
/**
 *  metodo para devolver el pago
 *
 * @method getPago
 * @public
 * @return {Pago} el pago
 * 
 */ 
 
getPago : function(){
    return this.__pago;
}

Por último crearemos la clase TPDV, que se encargará internamente de crear instancias de Pago y Venta y de pasar el pago a la venta.

var Pago = tienda.model.Pago;
 var Venta = tienda.model.Venta;
 
 /**
  * Modelo para la clase Tienda Putno de Venta
  * 
  * @class TPDV
  * @public
  * @extends qx.core.Object
  * @author Nilmar S�nchez Muguercia
  * @namespace tienda.model  
  * @copyrigth 
  * @license
  * @version 1.4
  *
  */
 
qx.Class.define("tienda.model.TPDV",
{
    extend : qx.core.Object,
 
    /**
     * @property
     */
    properties :
    {
        /**
         * propiedad de los pago
         * 
         * @name __pagos
         * @private
         * @type {Array}
         * 
         */
        __pagos : {},
 
        /**
         * propiedad de las ventas
         * 
         * @name __ventas
         * @private
         * @type {Array}
         * 
         */
        __ventas : {}        
    },
 
    /**
     * metodo de inicializacion de la clase
     *
     * @constructor
     * @public     
     *
     */
 
    construct : function()
    {
        this.__pagos = [];
        this.__ventas = [];
    },
 
   /**
     * @method
     */
    members :
    {
        /**
         *  metodo para efectuar el pago
         *
         * @method efectuarPago
         * @public         
         * @return {Boolean} si el pago fue satisfactorio o no
         * 
         */ 
 
        efectuarPago : function(){
            for(var i=0;i<this.__ventas.length;i++){
                if(this.__ventas[i].getPago().getMonto() < this.__ventas[i].total()){
                    throw new Error('Dinero incompleto');
                    return false;
                }
                return true;
            }
        },
 
        /**
         *  metodo para agregar un pagos
         *
         * @method addPago
         * @public         
         * @return {TPDV}
         */ 
 
        addPago : function(pago){
            this.__pagos.push(new Pago(pago));
            return this;
        },
 
        /**
         *  metodo para agregar un venta
         *  se le agrega el ultimo pago efectuado
         *
         * @method addVenta
         * @public         
         * @param ventas {Array} arreglo de objetos de las ventas
         * @return {TPDV}
         */ 
 
        addVenta : function(ventas){            
            var venta = new Venta();
            for(var i=0;i<ventas.length;i++){
                venta.crearLineaProducto(ventas[i].producto,ventas[i].cantidad);
            }
            venta.setPago(this.__pagos[this.__pagos.length-1]);
            this.__ventas.push(venta);
            return this;
        }
 
    }
});

De esta manera ejecutando el código creado, en el main de Application:



var producto1 = new EspecificacionProducto("Primer producto",5.2);
var producto2 = new EspecificacionProducto("Segundo producto",1.25);
var producto3 = new EspecificacionProducto("Tercer producto",2.2);
 
var ventas = [];
ventas.push({'producto':producto1,'cantidad':2});//10.4
ventas.push({'producto':producto2,'cantidad':1});//1.25
ventas.push({'producto':producto3,'cantidad':3});//6.6
 
var tpdv = new TPDV();
tpdv.addPago(20.00);//se puede pagar la venta de los productos anteriores 18.25
tpdv.addVenta(ventas);
 
try{
    tpdv.efectuarPago();
}catch(Exception){
    console.log(Exception.message);
}

Codificación Opción 2:

Para llevar a cabo la codificación de la segunda opción y disminuir la dependencia de la clase TPDV lo primero será modificar la clase Venta, donde al constructor se le pasará el monto a pagar de esa venta, se eliminaran los métodos setPago y getPago, ya que Venta crea internamente una instancia de la clase Pago, y se crea un método nuevo llamado efectuarPago, en el cual se comprobará si el monto pasado alcanza para la venta efectuada.



//en el constructor de Venta
this.__pago = new Pago(monto);
 
//metodo nuevo para comprobar el pago
/**
 *  metodo para efectuar el pago
 *
 * @method efectuarPago
 * @public
 * @return {Boolean} si el pago fue satisfactorio o no
 * 
 */
 
efectuarPago : function(){
    if(this.__pago.getMonto() < this.total()){
        throw new Error('Dinero incompleto');
        return false;
    }
    return true;
}


Por otro lado la clase TPDV también cambia pues ya no contiene instancias de Pago, por lo tanto se le retira ese atributo. El método efectuarPago se simplifica y se agrega un parámetro al método addVenta, para pasar el monto de la venta.



//metodo de efectuar pago
/**
 *  metodo para efectuar el pago
 *
 * @method efectuarPago
 * @public         
 * 
 */ 
 
efectuarPago : function(){
    for(var i=0;i<this.__ventas.length;i++){
        this.__ventas[i].efectuarPago();
    }
},
 
//metodo de agregar una venta
/**
 *  metodo para agregar un venta
 *  se le agrega el ultimo pago efectuado
 *
 * @method addVenta
 * @public        
 * @param ventas {Array} arreglo de objetos de las ventas 
 * @param monto {Float} monto a pagar
 * 
 */ 
 
addVenta : function(ventas,monto){            
    var venta = new Venta(monto);
    for(var i=0;i<ventas.length;i++){
        venta.crearLineaProducto(ventas[i].producto,ventas[i].cantidad);
    }            
    this.__ventas.push(venta);
}

De esta manera es la Venta, quien comprueba si el monto pasado la satisface o no, quedando de la siguiente manera el main de Application:

 



var producto1 = new EspecificacionProducto("Primer producto",5.2);
var producto2 = new EspecificacionProducto("Segundo producto",1.25);
var producto3 = new EspecificacionProducto("Tercer producto",2.2);
 
var ventas = [];
ventas.push({'producto':producto1,'cantidad':2});//10.4
ventas.push({'producto':producto2,'cantidad':1});//1.25
ventas.push({'producto':producto3,'cantidad':3});//6.6
 
var tpdv = new TPDV();
tpdv.addVenta(ventas,25.34);
 
try{
    tpdv.efectuarPago();
}catch(Exception){
    console.log(Exception.message);
}


El código aquí mostrado es puramente didáctico.

Hasta aquí la tercera parte del artículo de los patrones GRASP, espero dejen sus impresiones. En el próximo estaremos hablando del patrón Alta Cohesión.

Etiquetado en:

2 Comentarios

  1. Y@i$el dice:

    Me parece interesante ¿Donde puedo obtener Criax III y su respectiva documentación?

Dejar un comentario

¿Eres humano? Entonces resuelve esta operación: * Límite de tiempo se agote. Por favor, recargar el CAPTCHA por favor.