1. Code
  2. PHP

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

Single Responsibility (SRP), Open/Closed (OCP), Liskov Substitution, Interface Segregation, and Dependency Inversion. Cinco principios ágiles que deberían guiarte cada vez que escribas un código.
Scroll to top
This post is part of a series called The SOLID Principles.
SOLID: Part 3 - Liskov Substitution & Interface Segregation Principles

Spanish (Español) translation by honeymmmm (you can also view the original English article)

Single Responsibility (SRP)Open/Closed (OCP)Liskov Substitution, Interface Segregation, and Dependency Inversion. 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 el 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 más tarde se reeditó en la versión C # del libro Agile Principles, Patterns, and Practices in C#, y es el último de los cinco principios ágiles de SOLID.

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 regresamos 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 manera que es difícil de describir. Trataré de 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 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 las variables globales $_GET o $_POST. Pero ¿por qué es esto malo?

html-php-sql-cross-dependencieshtml-php-sql-cross-dependencieshtml-php-sql-cross-dependencies

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 para 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.

html-php-sql-stored-procedureshtml-php-sql-stored-procedureshtml-php-sql-stored-procedures

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 realizan 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 sonar indignante 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 mejorar. 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.

HighLevelDesignHighLevelDesignHighLevelDesign

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 necesitará 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 Evolving Toward a Persistence Layer.
  • Finalmente, a la derecha de la lógica empresarial, fuera de ella, tenemos todas las clases que están creando clases de lógica empresarial. 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.

1
class Test extends PHPUnit_Framework_TestCase {
2
3
  function testItCanReadAPDFBook() {
4
		$b = new PDFBook();
5
		$r = new PDFReader($b);
6
7
		$this->assertRegExp('/pdf book/', $r->read());
8
	}
9
10
}
11
12
class PDFReader {
13
14
	private $book;
15
16
	function __construct(PDFBook $book) {
17
		$this->book = $book;
18
	}
19
20
	function read() {
21
		return $this->book->read();
22
	}
23
24
}
25
26
class PDFBook {
27
28
	function read() {
29
		return "reading a pdf book.";
30
	}
31
}

Comenzamos a desarrollar nuestro e-reader como un lector de PDF. Hasta ahora tan bueno. Tenemos una clase de PDFReader usando un PDFBook. La función de read() en el lector delega en el método de read() del libro. Simplemente verificamos esto haciendo una verificación de expresiones regulares después de una parte clave de la cadena devuelta por el método reader() de PDFBook.

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.

pdfreader-pdfbookpdfreader-pdfbookpdfreader-pdfbook

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.

1
class Test extends PHPUnit_Framework_TestCase {
2
3
	function testItCanReadAPDFBook() {
4
		$b = new PDFBook();
5
		$r = new EBookReader($b);
6
7
		$this->assertRegExp('/pdf book/', $r->read());
8
	}
9
10
}
11
12
class EBookReader {
13
14
	private $book;
15
16
	function __construct(PDFBook $book) {
17
		$this->book = $book;
18
	}
19
20
	function read() {
21
		return $this->book->read();
22
	}
23
24
}
25
26
class PDFBook {
27
28
	function read() {
29
		return "reading a pdf book.";
30
	}
31
}

El cambio de nombre no tenía efectos contrarios funcionales. Las pruebas todavía están 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 efecto de diseño serio.

ebookreader-pdfbookebookreader-pdfbookebookreader-pdfbook

Nuestro lector se volvió mucho más abstracto. Mucho más general. Tenemos un EBookReader genérico que utiliza un tipo de libro muy específico, PDFBook. 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.

1
class Test extends PHPUnit_Framework_TestCase {
2
3
	function testItCanReadAPDFBook() {
4
		$b = new PDFBook();
5
		$r = new EBookReader($b);
6
7
		$this->assertRegExp('/pdf book/', $r->read());
8
	}
9
10
}
11
12
interface EBook {
13
	function read();
14
}
15
16
class EBookReader {
17
18
	private $book;
19
20
	function __construct(EBook $book) {
21
		$this->book = $book;
22
	}
23
24
	function read() {
25
		return $this->book->read();
26
	}
27
28
}
29
30
class PDFBook implements EBook{
31
32
	function read() {
33
		return "reading a pdf book.";
34
	}
35
}

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 EBook y representa las necesidades del EBookReader. Este es un resultado directo de respetar el Interface Segregation Principle  (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 que un EBookReader use EBooks y tenga un método de read().

ebookreader-ebookinterface-pdfbookebookreader-ebookinterface-pdfbookebookreader-ebookinterface-pdfbook

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

  • Los primeros puntos de dependencia de EBookReader hacia la interfaz de EBook son de uso de tipo. EBookReader utiliza EBooks.
  • La segunda dependencia es diferente. Apunta desde PDFBook hacia la misma interfaz de EBook, pero es de tipo implementación. Un PDFBook es solo una forma particular de EBook, y por lo tanto 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 interfaz de EBook e implementarla.

1
class Test extends PHPUnit_Framework_TestCase {
2
3
	function testItCanReadAPDFBook() {
4
		$b = new PDFBook();
5
		$r = new EBookReader($b);
6
7
		$this->assertRegExp('/pdf book/', $r->read());
8
	}
9
10
	function testItCanReadAMobiBook() {
11
		$b = new MobiBook();
12
		$r = new EBookReader($b);
13
14
		$this->assertRegExp('/mobi book/', $r->read());
15
	}
16
17
}
18
19
interface EBook {
20
	function read();
21
}
22
23
class EBookReader {
24
25
	private $book;
26
27
	function __construct(EBook $book) {
28
		$this->book = $book;
29
	}
30
31
	function read() {
32
		return $this->book->read();
33
	}
34
35
}
36
37
class PDFBook implements EBook {
38
39
	function read() {
40
		return "reading a pdf book.";
41
	}
42
}
43
44
class MobiBook implements EBook {
45
46
	function read() {
47
		return "reading a mobi book.";
48
	}
49
}

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é por completo la forma en que pienso 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 Sólidos, 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é debería ser diferente su código?