Como sabemos la Programacion Orientada a Objetos nos ayuda a diseñar software de una forma sencilla, ¿pero que pasa cuando aun usando este enfoque de programación nuestro código se vuelve complejo y dificil de entender?
Esto es lo que SOLID resuelve, siendo una serie de principios o guías de diseño que nos ayudan en la labor de crear programas legibles y mantenibles.
Estos principios, hacen que sea fácil para un programador desarrollar software que sea fácil de mantener y ampliar. También facilitan a los desarrolladores refactorizar fácilmente el código y forman parte del desarrollo ágil de software ademas de que favorece una mayor reusabilidad y calidad del código, así como la encapsulación.
- Mantenimiento del código más fácil y rápido
- Permite añadir nuevas funcionalidades de forma más sencilla
- Favorece una mayor reusabilidad y calidad del código, así como la encapsulación
Establece que cada clase debe tener una sola función o tarea a cumplir
- Forma incorrecta
classBook{getTitle(): string{return"Titulo"}getAuthor(): string{return"Autor"}turnPage(): string{return"Siguiente página"}printCurrentPage(): string{return"Imprimir página"}}Como vemos una misma clase ademas de tener los getters tambien tiene la funcion de paginado asi como de imprimir.
- Forma correcta
classBook{getTitle(): string{return"Titulo"}getAuthor(): string{return"Autor"}}classPager{gotoPrevPage(): string{return"Anterior página"}gotoNextPage(): string{return"Siguiente página"}gotoPageByPageNumber(pagerNumber: number): string{return"Ir a la página"}}classPrinter{printPageInHTML(pagina: any): string{return"Imprimir página"}printPageInJSON(pagina: any): string{return"Imprimir página"}printPageInXML(pagina: any): string{return"Imprimir página"}}Se seperan las funciones en diferentes clases.
Establece que las clases o entidades deberían estar abiertos para su extensión, pero cerrados para su modificación.
- Forma incorrecta
classPdf{constructor(name,size){this.name=name;this.size=size;}// ...}classCsv{constructor(name){this.name=name;}// ...}classPrinter{functionprintFile(file){if(fileinstanceofPdf){// Print Pdf...}elseif(fileinstanceofCsv){// Print Csv...}}}Notamos que la clase Printer sufriria cambios cada vez que se agreguen mas extensiones de archivos, lo que va en contra de este principio.
- Forma correcta
classPrintable{print(){// ...}}classPdfextendsPrintable{constructor(name: string,size: number){super();this.name=name;this.size=size;}// Overrideprint(){// ...}}classCsvextendsPrintable{constructor(name: string){super();this.name=name;}// Overrideprint(){// ...}}classPrinter{printFile(file: Printable){file.print();}}Ahora la clase Printer no necesitaria saber que extension tiene el documento para poder imprimir.
Declara que una subclase debe ser sustituible por su superclase, y si al hacer esto, el programa falla, estaremos violando este principio.
classRectangle{constructor(){this.width=0;this.height=0;}setwidth(width: number){this.width=width;}setheight(height: number){this.height=height;}getarea(): number{returnthis.width*this.height;}}classSquareextendsRectangle{setwidth(width: number){this.width=width;this.height=width;}setheight(height: number){this.width=height;this.height=height;}}functionrenderLargeRectangles(rectangles){rectangles.forEach((rectangle)=>{rectangle.width(4);rectangle.height(5);constarea=rectangle.getArea();});}constrectangles=[newRectangle(),newRectangle(),newSquare()];renderLargeRectangles(rectangles);Como vemos, Squeare sobrescribe la clase Rectangle en los setters por lo que no se cumpliria este princicpio.
- Forma correcta
classRectangle{constructor(width: number,height: number){this.width=width;this.height=height;}getarea(): number{returnthis.width*this.height;}}classSquareextendsRectangle{constructor(size: number){super(size,size);}}renderLargeRectangles(rectangles: []){rectangles.forEach((rectangle)=>{constarea=rectangle.area();});}constrectangles=[newRectangle(4,5),newRectangle(4,5),newSquare(4)];renderLargeRectangles(rectangles);Este principio define que una clase no debe ser forzada a depender de interfaces(propiedades o métodos) que no usa.
- Ejemplo:
interfaceAnimal{fly();run();}classEagleimplementsAnimal{fly(){}run(){}}classDogimplementsAnimal{fly(){}run(){}}Como vemos, Dog hereda el método fly, algo que no es coherente.
- Forma correcta
interfaceFlyingAnimal{fly()}interfaceFlightlessAnimal{run()}classEagleimplementsFlyingAnimal{fly(){}}classOstrichimplementsFlightlessAnimal{run(){}}El principio DIP dice que si una clase depende de otras clases, esta relación debería estar en las interfaces.
- Forma incorrecta
classCement{// implementation}classBrick{// implementation}classHome{constructor(cement: number,brick: number){this.cement=cementthis.brick=brick}cost(): number{// calculate the cost}}La clase Building tiene que conocer la clase Cement para poder calcular el coste de la construcción
classMaterial{// implementation}classBrickextendsMaterial{// implementation}classCementextendsMaterial{// implementation}classConstruction{constructor(material: Material){this.material=material}cost(): number{// calculate the cost}}classHomeextendsConstruction{cost(): number{// calculate the cost}}