BLOG

Lunes, 09 Octubre 2017 12:41

PRINCIPIOS S.O.L.I.D. USANDO TYPESCRIPT

Cristian Villegas   En esta oportunidad Cristian Villegas Chávez, destacado developer en Trans Solutions Systems, nos   mostrará como aplicar los cinco principios S.O.L.I.D. usando Typescript.

 Los principios están un nivel de abstracción por encima de los patrones de diseño y están también por   encima del lenguaje que estamos utilizando, son conceptos a tener en cuenta mientras se desarrolla.   Esta vez vamos a aplicar los conceptos usando typescript un proyecto con angular.

  Para poder describir los principios S.O.L.I.D. usando Typescript vamos a crear un proyecto de Angular   denominado solidtypescript; usamos la herramienta angular-cli https://cli.angular.io que nos permite   inicializar, desarrollar y mantener nuestras aplicaciones; de antemano ya debemos tener instalado el   nodejs https://nodejs.org. Y de forma global el cli para utilizar los comandos.

Tenemos la siguiente estructura base de nuestro proyecto Angular con typescript en el cual crearemos las clases, interfaces, componentes, servicios necesarios para explicar en qué momento estamos aplicando los conceptos.

principios SOLID

Para explicar el primer principio Single Responsibility Principle / Principio de Responsabilidad Única tenemos que tener en claro los siguientes puntos.

-      Cada clase tiene que tener una única responsabilidad

-      Una única razón para cambiar

Tenemos la clase employee.ts

export class Employee {
   getDNI(): string{
       return "46046...";
   }
   getName(): string {
       return "Cristian";
   }
   getPhoneNumber(): string {
      return "963....";
   }
   getAddressPrincipal(): string {
       return "";
   }
}

 Se ve una clase sencilla que retorna el nombre, número de teléfono y dirección principal. Supongamos que queremos recuperar la dirección secundaria, a simple vista no se podría. Cambiemos esta clase según el principio.

export class Employee {
 getDNI(): string{
 return "46046...";
 }
 getName(): string {
 return "Cristian";
 }
 getPhoneNumber(): string {
 return "963....";
 }
}
export class EmployeeAddress{
getAddress(priority: number): string{
        return "";
 }

Ahora podemos decir que cada clase tiene una única responsabilidad y se ve más entendible. 

Open-Closed Principle / Principio Abierto-Cerrado para este principio debemos tener en claro que los objetos o entidades deben estar abiertos para la extensión, pero cerrados para su modificación. 

A nuestra clase employee agreguemos algunos campos para para aplicar este principio.

export class Employee { 

 private dni: string;
 private name: string;
 private phoneNumber: string;
 protected salary: number;
 
constructor(dni: string, name: string, phoneNumber:string, salary: number){
 this.dni=dni;
 this.name=name;
 this.phoneNumber=phoneNumber;
 this.salary=salary;
 }
 getSalary(): number {
 return this.salary;
 }
}

export class EmployeeOnPayroll extends Employee { 

 constructor(dni:string, name: string, phoneNumber, salary:number){
 super(dni, name, phoneNumber, salary);
 }
 getSalary():number {
 return super.getSalary()*0.05;
 }
}
export class EmployeeOnContract extends Employee { 
 
  constructor(dni:string, name: string, phoneNumber, type:string, salary:number){
 super(dni, name, phoneNumber, salary);
 }
 getSalary():number {
 return super.getSalary()*0.03;
 }
}

La clase employee tiene un método para recuperar el salario dependiendo del tipo de empleado si se encuentra en planilla o recibo por honorarios.

Para este caso creamos dos clases un EmployeeOnPayroll  y EmployeeOnContract en cada una de ellas se puede determinar de distinta forma sus salarios. De esta forma podemos seguir extendiendo según la necesidad.

Como habíamos notado Typescript tiene una peculiaridad para sobre escribir el método de la clase principal.

Liskov Substitution es una variación del principio de abierto cerrado, los tipos derivados deben ser completamente sustituibles por sus tipo base, es decir si una función recibe un objeto como parámetro de tipo A y en su lugar le pasamos otro tipo B que hereda de A, dicha función debe proceder correctamente.

import { Employee, EmployeeOnContract, EmployeeOnPayroll } from 
'./../o/employee';

export class EmployeeWrite {

constructor(employee: Employee){
 console.log(employee.getSalary());
  }

}

let employeeOnContract: EmployeeOnContract= new
EmployeeOnContract("46046...","Employee","963.....",4000);
let employeeWrite: EmployeeWrite= new
EmployeeWrite(this.employeeOnContract);

Si nos fijamos la definición de la clase EmployeeWrite, su constructor tiene un parámetro de tipo Employee; sin embargo, en este ejemplo al instanciar la clase EmployeeWrite en su constructor se asigna un objeto de tipo EmployeeOnContract; esto es correcto porque lleva la herencia de la clase Empoyee.

Interface Segregation Principle / Principio de segregación de interfaz está muy unido al de responsabilidad única, un cliente nunca puede ser forzado a implementar una interfaz que no utiliza o los clientes no deben verse obligados a depender de métodos que no utilizan.

export interface  IBasecrud {
     create(oObject: any);   
     update(oObject: any);
     delete(oObject: any);
     searchWithPagination(criteriaPagination: any);
 }
 export class  Employee  implements  IBasecrud {
      create(oObject: any){
 }
      update(oObject: any){
}
       delete(oObject: any){
      }
      searchWithPagination(criteriaPagination: any){
      }
}
  export class  EmployeeAddress  implements  IBasecrud {
       create(oObject: any){
}
update(oObject: any){
}
      delete(oObject: any){
      }
      searchWithPagination(criteriaPagination: any){
      }
                     }

En el código de clase EmployeeAddress también implementa el searchWithPagination, se trata de la paginación de los registros de las direcciones del empleado. No tiene mucho sentido implementar este método, por lo mismo la recomendación para realizar búsqueda con paginación es que se tenga buena cantidad de registros y en teoría no creo que pase más de 10 direcciones por cada empleado. Las implementaciones anteriores no cumplen el principio de segregación de interfaces.

Para solucionar creamos las siguientes interfaces e implementamos de acuerdo a lo que se necesite.

export interface  IBasecud {    
 create(oObject: any);    
 update(oObject: any);
 delete(oObject: any);
 }
 export interface  IBasesearch {
   searchWithPagination(criteriaPagination: any);
}
export class  Employee  implements  IBasecud, IBasesearch {
   
          create(oObject: any){
     }
    update(oObject: any){
    }
    delete(oObject: any){
    }
    searchWithPagination(criteriaPagination: any){
    }
}
export class  EmployeeAddress  implements  IBasecud {
    
    create(oObject: any){
    }
    
    update(oObject: any){
    }
    delete(oObject: any){
    }
}

Dependency inversion principle / Principio de inversión de dependencias menciona que los módulos de alto nivel no deben depender de módulos de bajo nivel. Más bien ambos deben depender de abstracciones.

Planteamos que queremos configurar punto de conexión a WEB API para cliente angular.

export class SettingEndPoint{
  
   setting(parameters: any){
   }
}

Ahora queremos cambiar para conectarnos a distintos WEB APIs con alguna variación en sus parámetros.

Así como se muestra la clase no nos permitiría añadir.

Para soportar el cambio solicitado lo recomendable es crear una interface e implementar en una clase cada vez que se necesario.

export interface  ISettingEndPoint {
   setting(parameters: any);
}
export class  SettingEndPointAPIInHouse  implements  ISettingEndPoint {
   setting(inHouseParameters: any){
   }
}
export class  SettingEndPointAPIExternals  implements  ISettingEndPoint {
   setting(externalParameters: any){
   }
          }

Los archivos de las clases pueden encontrar en el siguiente repositorio de github

https://github.com/cnvillegaschavez/solidtypescript

Conclusión: De igual manera que se aplican los conceptos SOLID en los diferentes lenguajes de programación, TypeScript no los excluye. Recomiendo que todos los desarrolladores tengan en claro estos conceptos para tener proyectos escalables.