SÓLIDO Parte 4 - El principio de inversión de dependencia

La responsabilidad única (SRP), abierta / cerrada (OCP), sustitución de Liskov, segregación de interfaz y Inversión de dependencia. Cinco principios ágiles que deberían guiarte cada vez que escribas un código..

Sería injusto decirle que cualquiera de los principios de SOLID es más importante que otro. Sin embargo, probablemente ninguno de los otros tenga un efecto tan inmediato y profundo en su código como el Principio de Inversión de Dependencia, o DIP en breve. Si encuentra que los otros principios son difíciles de entender o aplicar, comience con este y aplique el resto en el código que ya respeta DIP.

Definición

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

Este principio fue definido por Robert C. Martin en su libro Agile Software Development, Principles, Patterns, and Practices y luego publicado de nuevo en la versión C # del libro Agile Principles, Patterns, and Practices in C #, y es el último de los cinco. Sólidos principios ágiles..

DIP en el mundo real

Antes de comenzar a programar, me gustaría contarles una historia. En Syneto, no siempre fuimos muy cuidadosos con nuestro código. Hace unos años sabíamos menos y aunque intentamos dar lo mejor de nosotros, no todos nuestros proyectos fueron tan buenos. Pasamos por el infierno y volvimos y aprendimos muchas cosas por ensayo y error..

Los principios de SOLID y los principios de arquitectura limpia del tío Bob (Robert C. Martin) se convirtieron en un cambio de juego para nosotros y transformaron nuestra forma de codificación de una manera que es difícil de describir. Intentaré ejemplificar, en pocas palabras, algunas decisiones arquitectónicas clave impuestas por el DIP que tuvieron un gran impacto en nuestros proyectos..

La mayoría de los proyectos web contienen tres tecnologías principales: HTML, PHP y SQL. La versión particular de estas aplicaciones de las que estamos hablando o el tipo de implementaciones de SQL que utiliza es irrelevante. La cuestión es que la información de un formulario HTML debe terminar, de una forma u otra, en la base de datos. El pegamento entre los dos se puede proporcionar con PHP.

Lo que es esencial quitar de esto es que las tres tecnologías representan tres capas arquitectónicas diferentes: interfaz de usuario, lógica de negocios y persistencia. Hablaremos sobre las implicaciones de estas capas en un minuto. Por ahora, concentrémonos en algunas soluciones extrañas pero frecuentes para hacer que las tecnologías funcionen juntas..

Muchas veces he visto proyectos que usaban código SQL en una etiqueta PHP dentro de un archivo HTML, o código PHP haciendo eco de páginas y páginas de HTML e interpretando directamente el $ _GET o $ _POST variables globales. Pero ¿por qué es tan malo??


Las imágenes de arriba representan una versión en bruto de lo que describimos en el párrafo anterior. Las flechas representan varias dependencias, y como podemos concluir, básicamente todo depende de todo. Si necesitamos cambiar una tabla de base de datos, podemos terminar editando un archivo HTML. O si cambiamos un campo en HTML, podemos terminar cambiando el nombre de una columna en una declaración SQL. O si observamos el segundo esquema, es posible que tengamos que modificar nuestro PHP si el HTML cambia, o en casos muy graves, cuando generemos todo el contenido HTML desde un archivo PHP, seguramente necesitaremos cambiar un archivo PHP Modificar el contenido HTML. Entonces, no hay duda, las dependencias están en zigzag entre las clases y los módulos. Pero no termina aquí. Puede almacenar procedimientos; Código PHP en tablas SQL.


En el esquema anterior, las consultas a la base de datos SQL devuelven el código PHP generado con datos de las tablas. Estas funciones o clases de PHP están realizando otras consultas SQL que devuelven códigos PHP diferentes, y el ciclo continúa hasta que finalmente se obtiene y devuelve toda la información ... probablemente a la interfaz de usuario.

Sé que esto puede parecer escandaloso para muchos de ustedes, pero si aún no ha trabajado con un proyecto inventado e implementado de esta manera, seguramente lo hará en su futura carrera. La mayoría de los proyectos existentes, independientemente de los lenguajes de programación utilizados, fueron escritos con principios anteriores en mente, por programadores a los que no les importaba o no sabían lo suficiente como para hacerlo mejor. Si estás leyendo estos tutoriales, lo más probable es que estés en un nivel más alto. Estás listo o te estás preparando para respetar tu profesión, para abrazar tu oficio y para hacerlo mejor..

La otra opción es repetir los errores que cometieron sus antecesores y vivir con las consecuencias. En Syneto, después de que uno de nuestros proyectos alcanzó un estado casi imposible de mantener debido a su arquitectura antigua y dependiente de todos y tuvimos que abandonarlo para siempre, decidimos no volver a recorrer ese camino. Desde entonces, nos hemos esforzado por tener una arquitectura limpia que respete correctamente los principios de SOLID y, lo más importante, el principio de inversión de dependencia..


Lo sorprendente de esta arquitectura es cómo apuntan las dependencias:

  • La interfaz de usuario (en la mayoría de los casos, un marco web de MVC) o cualquier otro mecanismo de entrega que haya para su proyecto dependerá de la lógica empresarial. La lógica de negocios es bastante abstracta. Una interfaz de usuario es muy concreta. La interfaz de usuario es solo un detalle para el proyecto, y también es muy volátil. Nada debe depender de la interfaz de usuario, nada debe depender de su marco MVC.
  • La otra observación interesante que podemos hacer es que la persistencia, la base de datos, su MySQL o PostgreSQL, depende de la lógica empresarial. Su lógica de negocios es la base de datos independiente. Esto permite intercambiar la persistencia que desees. Si mañana desea cambiar MySQL con PostgreSQL o solo archivos de texto sin formato, puede hacerlo. Por supuesto, deberá implementar una capa de persistencia específica para el nuevo método de persistencia, pero no tendrá que modificar una sola línea de código en su lógica empresarial. Hay una explicación más detallada sobre el tema de la persistencia en el tutorial Cómo evolucionar hacia una capa de persistencia.
  • Finalmente, a la derecha de la lógica de negocios, fuera de ella, tenemos todas las clases que están creando clases de lógica de negocios. Estas son fábricas y clases creadas por el punto de entrada a nuestra aplicación. Muchas personas tienden a pensar que estas pertenecen a la lógica de negocios, pero mientras crean objetos de negocios, su única razón es hacerlo. Son clases solo para ayudarnos a crear otras clases. Los objetos comerciales y la lógica que proporcionan son independientes de estas fábricas. Podríamos usar diferentes patrones, como Simple Factory, Abstract Factory, Builder o creación de objetos simples para proporcionar la lógica empresarial. No importa. Una vez que se crean los objetos de negocio, pueden hacer su trabajo.

Muéstrame el código

La aplicación del principio de inversión de dependencia (DIP) a nivel arquitectónico es bastante fácil si respeta los patrones de diseño ágil clásico. Ejercitarlo y ejemplificarlo dentro de la lógica de negocios también es bastante fácil e incluso puede ser divertido. Imaginaremos una aplicación de lector de libros electrónicos..

class Test extiende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuevo PDFReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  class PDFReader private $ book; función __construir (libro PDFBook $) $ this-> book = $ libro;  función read () return $ this-> book-> read ();  class PDFBook function read () return "leyendo un libro en pdf."; 

Comenzamos a desarrollar nuestro e-reader como un lector de PDF. Hasta ahora tan bueno. Tenemos una Lector PDF clase usando un Libro PDF. los leer() Función en el lector delegados a la del libro. leer() método. Simplemente verificamos esto haciendo una verificación de expresiones regulares después de una parte clave de la cadena devuelta por Libro PDFes lector() método.

Por favor, tenga en cuenta que esto es sólo un ejemplo. No implementaremos la lógica de lectura de archivos PDF u otros formatos de archivo. Es por eso que nuestras pruebas simplemente comprobarán algunas cadenas básicas. Si tuviéramos que escribir la aplicación real, la única diferencia sería cómo probamos los diferentes formatos de archivo. La estructura de dependencia sería muy similar a nuestro ejemplo..


Tener un lector de PDF utilizando un libro PDF puede ser una solución sólida para una aplicación limitada. Si nuestro objetivo fuera escribir un lector de PDF y nada más, sería una solución aceptable. Pero queremos escribir un lector genérico de libros electrónicos que admita varios formatos, entre los cuales se encuentra nuestra primera versión en PDF. Vamos a cambiar el nombre de nuestra clase de lector.

class Test extiende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuevo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  class EBookReader private $ book; función __construir (libro PDFBook $) $ this-> book = $ libro;  función read () return $ this-> book-> read ();  class PDFBook function read () return "leyendo un libro en pdf."; 

El cambio de nombre no tenía efectos contrarios funcionales. Las pruebas siguen pasando.

Las pruebas comenzaron a la 1:04 pm ...
PHPUnit 3.7.28 por Sebastian Bergmann.
Tiempo: 13 ms, Memoria: 2.50Mb
OK (1 prueba, 1 aserción)
Proceso terminado con código de salida 0

Pero tiene un serio efecto de diseño..


Nuestro lector se volvió mucho más abstracto. Mucho más general. Tenemos un genérico Lector de libros electrónicos que utiliza un tipo de libro muy específico, Libro PDF. Una abstracción depende de un detalle. El hecho de que nuestro libro sea de tipo PDF debería ser solo un detalle, y nadie debería depender de él..

class Test extiende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuevo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  interface EBook function read ();  class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book;  función read () return $ this-> book-> read ();  clase PDFBook implementa EBook function read () return "leyendo un libro en pdf"; 

La solución más común y más utilizada para invertir la dependencia es introducir un módulo más abstracto en nuestro diseño. "El elemento más abstracto en OOP es una interfaz. Por lo tanto, cualquier otra clase puede depender de una interfaz y seguir respetando DIP"..

Creamos una interfaz para nuestro lector. La interfaz se llama Libro electronico y representa las necesidades de la Lector de libros electrónicos. Este es un resultado directo de respetar el Principio de Segregación de Interfaz (ISP) que promueve la idea de que las interfaces deben reflejar las necesidades de los clientes. Las interfaces pertenecen a los clientes y, por lo tanto, se nombran para reflejar los tipos y objetos que necesitan los clientes y contendrán los métodos que los clientes desean utilizar. Es natural para un Lector de libros electrónicos usar Libros electrónicos y tener un leer() método.


En lugar de una sola dependencia, ahora tenemos dos dependencias.

  • Los primeros puntos de dependencia de Lector de libros electrónicos hacia Libro electronico Interfaz y es de uso tipográfico.. Lector de libros electrónicos usos Libros electrónicos.
  • La segunda dependencia es diferente. Apunta desde Libro PDF hacia el mismo Libro electronico Interfaz pero es de tipo implementación. UNA Libro PDF es solo una forma particular de Libro electronico, y así implementa esa interfaz para satisfacer las necesidades del cliente..

Como era de esperar, esta solución también nos permite conectar diferentes tipos de libros electrónicos en nuestro lector. La única condición para todos estos libros es satisfacer la Libro electronico Interfaz e implementarlo.

class Test extiende PHPUnit_Framework_TestCase function testItCanReadAPDFBook () $ b = new PDFBook (); $ r = nuevo EBookReader ($ b); $ this-> assertRegExp ('/ pdf book /', $ r-> read ());  function testItCanReadAMobiBook () $ b = new MobiBook (); $ r = nuevo EBookReader ($ b); $ this-> assertRegExp ('/ mobi book /', $ r-> read ());  interface EBook function read ();  class EBookReader private $ book; function __construct (EBook $ book) $ this-> book = $ book;  función read () return $ this-> book-> read ();  clase PDFBook implementa EBook function read () return "leyendo un libro en pdf";  clase MobiBook implementa EBook function read () return "leyendo un libro mobi."; 

Lo que a su vez nos lleva al principio abierto / cerrado, y el círculo está cerrado..

El principio de inversión de dependencia es uno que nos guía o nos ayuda a respetar todos los demás principios. Respetando DIP:

  • Casi te obliga a respetar a OCP.
  • Te permite separar responsabilidades.
  • Hacerte usar correctamente los subtipos..
  • Te ofrecemos la oportunidad de segregar tus interfaces..

Pensamientos finales

Eso es. Hemos terminado. Todos los tutoriales sobre los principios de SOLID están completos. Para mí, personalmente, descubrir estos principios e implementar proyectos con ellos en mente fue un gran cambio. Cambié completamente mi forma de pensar sobre el diseño y la arquitectura y puedo decir que desde entonces todos los proyectos en los que trabajo son exponencialmente más fáciles de gestionar y comprender..

Considero que los principios SOLID son uno de los conceptos más esenciales del diseño orientado a objetos. Estos conceptos que deben guiarnos para mejorar nuestro código y nuestra vida como programadores son mucho más fáciles. El código bien diseñado es más fácil de entender para los programadores. Las computadoras son inteligentes, pueden entender el código sin importar su complejidad. Los seres humanos, por otro lado, tienen un número limitado de cosas que pueden mantener en su mente activa y enfocada. Más específicamente, el número de tales cosas es el Número Mágico Siete, Más o Menos Dos.

Debemos esforzarnos por tener nuestro código estructurado en torno a estos números y existen varias técnicas que nos ayudan a hacerlo. Funciones con un máximo de cuatro líneas de longitud (cinco con la línea de definición incluida) para que todas puedan caber al mismo tiempo dentro de nuestra mente. Las sangrías no superan los cinco niveles de profundidad. Clases con no más de nueve métodos. Diseña patrones que usualmente usan un número de cinco a nueve clases. Nuestro diseño de alto nivel en los esquemas anteriores utiliza de cuatro a cinco conceptos. Hay cinco principios SOLID, cada uno de los cuales requiere de cinco a nueve sub-conceptos / módulos / clases para ser ejemplificados. El tamaño ideal de un equipo de programación es entre cinco y nueve. El número ideal de equipos en una empresa es entre cinco y nueve..

Como puede ver, el número mágico siete, más o menos dos está a nuestro alrededor, ¿por qué su código debería ser diferente??