principios de SOLID explicados en PHP (Laravel)

Escribir programas de computadora es muy divertido. A menos que tenga que trabajar con el código de otras personas.

Si ha trabajado como desarrollador profesional durante más de tres días, sabe que nuestros trabajos son cualquier cosa menos creativos y emocionantes. Una parte de la razón es el liderazgo de la empresa (léase: personas que nunca lo entienden), mientras que la otra parte es la complejidad del código con el que tenemos que trabajar. Ahora bien, si bien no podemos hacer absolutamente nada sobre lo primero, podemos hacer mucho sobre lo segundo.

Entonces, ¿por qué las bases de código son tan complejas que tenemos ganas de destriparnos? Simplemente porque la gente que escribió la primera versión tenía prisa, y los que vinieron después siguieron aumentando el lío. El resultado final: un lío espeso que muy pocos quieren tocar y nadie entiende.

¡Bienvenido al primer día de trabajo!

Pero no tiene por qué ser así.

Escribiendo buen código, código modular y fácil de mantener, no es tan difícil. Solo cinco principios simples, establecidos desde hace mucho tiempo y bien conocidos, si se siguen con disciplina, garantizarán que su código sea legible, para los demás y para usted cuando lo vea seis meses después. 😂

Estos principios rectores están representados por el acrónimo SOLID. Quizás haya oído hablar del término “principios SÓLIDOS” antes, quizás no. En caso de que haya estado posponiendo este aprendizaje para “algún día”, bueno, ¡asegurémonos de que hoy sea ese día!

Entonces, sin más preámbulos, veamos de qué se trata todo esto SOLID y cómo puede ayudarnos a escribir un código realmente ordenado.

“S” is for Single Responsibility

Si observa diferentes fuentes que describen el principio de responsabilidad única, obtendrá algunas variaciones en su definición. Sin embargo, en términos más simples, se reduce a esto: cada clase en su base de código debe tener un rol muy específico; es decir, debería ser responsable por nada más que un soltero objetivo. Y cada vez que se necesita un cambio en esa clase, se deduce que necesitaremos cambiarlo solo porque esa responsabilidad específica ha cambiado.

Cuando me encontré con esto por primera vez, la definición que se me presentó fue: "Debe haber una, y solo una razón para que cambie una clase". Yo estaba como, “¡¿Qué ??! ¿Cambio? ¿Qué cambio? ¿Por qué cambiar? ”, Por eso dije antes que si lees sobre esto en diferentes lugares, obtendrás definiciones relacionadas pero algo diferentes y potencialmente confusas.

De todos modos, suficiente. Es hora de algo serio: si eres como yo, probablemente te estarás preguntando: “Está bien, todo bien. Pero, ¿por qué diablos debería importarme? No voy a empezar a escribir código en un estilo totalmente diferente al de mañana solo porque un loco que una vez escribió un libro (y ahora está muerto) lo diga ".

¡Excelente!

Y ese es el espíritu que debemos mantener si queremos verdaderamente aprende cosas. Entonces, ¿por qué toda esta canción y baile sobre “Single Responsibility” importa en absoluto? Diferentes personas explican esto de manera diferente, pero para mí, este principio se trata de traer disciplina y enfoque a su código.

Veamos un ejemplo antes de explicar mi interpretación. A diferencia de otros recursos que se encuentran en la web que brindan ejemplos que comprende pero que luego lo dejan preguntándose cómo lo ayudarían en casos del mundo real, vamos a sumergirnos en algo específico, un estilo de codificación que vemos una y otra vez, y tal vez incluso escribamos en nuestras aplicaciones de Laravel.

Cuando una aplicación de Laravel recibe una solicitud web, la URL se compara con las rutas que ha definido en web.php y api.php, y si hay una coincidencia, los datos de la solicitud llegan al controlador. Así es como se ve un método de controlador típico en aplicaciones reales a nivel de producción:

class UserController extends Controller {     public function store(Request $request)     {                 $validator = Validator::make($request->all(), [            'first_name' => 'required',            'last_name' => 'required',            'email' => 'required|email|unique:users',            'phone' => 'nullable'        ]);                 if ($validator->fails()) {             Session::flash('error', $validator->messages()->first());             return redirect()->back()->withInput();        }                // create new user        $user = User::create([            'first_name' => $request->first_name,            'last_name' => $request->last_name,            'email' => $request->email,            'phone' => $request->phone,        ]);                 return redirect()->route('login');     } }

Todos hemos escrito un código como este. Y es fácil ver lo que hace: registrar nuevos usuarios. Se ve bien y funciona bien, pero hay un problema: no está preparado para el futuro. Y por preparada para el futuro, me refiero a que no está preparada para manejar el cambio sin crear un lío.

¿Porque?

Puede decir que la función está destinada a rutas definidas en el web.php archivo; es decir, páginas tradicionales representadas por el servidor. Pasan unos días y ahora su cliente / empleador está desarrollando una aplicación móvil, lo que significa que esta ruta no será de utilidad para los usuarios que se registren desde dispositivos móviles. ¿Qué haces? Cree una ruta similar en el api.php archivo y escribir una función de controlador impulsada por JSON para él? Bien, ¿y luego qué? ¿Copiar todo el código de esta función, hacer algunos cambios y terminarlo? De hecho, esto es lo que están haciendo muchos desarrolladores, pero se están preparando para el fracaso.

El problema es que HTML y JSON no son los únicos formatos de API en el mundo (consideremos las páginas HTML como una API por el bien del argumento). ¿Qué pasa con un cliente que tiene un sistema heredado que se ejecuta en formato XML? Y luego hay otro para SOAP. Y gRPC. Y Dios sabe qué más vendrá al día siguiente.

Aún puede considerar crear un archivo separado para cada uno de estos tipos de API y copiar el código existente, modificándolo ligeramente. Seguro que hay diez archivos, diría usted, pero todo funciona bien, así que, ¿por qué quejarse? Pero luego viene el golpe, la némesis del desarrollo de software: el cambio. Suponga ahora que las necesidades de su cliente / empleador han cambiado. Ahora quieren que, en el momento del registro del usuario, registremos la dirección IP y agreguemos una opción para un campo que indique que han leído y entendido los términos y condiciones.

¡Oh, oh! No tenemos diez archivos para editar, y debemos asegurarnos de que la lógica se maneja exactamente igual en todos ellos. Incluso un solo error puede causar importantes pérdidas comerciales. Y ahora imagine el horror de las aplicaciones SaaS a gran escala, ya que la complejidad del código ya es bastante alta.

¿Cómo llegamos a este infierno?

La respuesta es que el método del controlador que parece tan inofensivo en realidad está haciendo varias cosas diferentes: está validando la solicitud entrante, está manejando redirecciones y está creando nuevos usuarios.

¡Está haciendo demasiadas cosas! Y sí, como habrás notado, saber cómo crear nuevos usuarios en el sistema no debería ser un trabajo de métodos de controlador. Si tuviéramos que sacar esta lógica de la función y ponerla en una clase separada, ahora tendríamos dos clases, cada una con una única responsabilidad de manejar. Si bien estas clases pueden ayudarse mutuamente llamando a sus métodos, no se les permite saber qué está sucediendo dentro de la otra.

class UserController extends Controller {     public function store(Request $request)     {                 $validator = Validator::make($request->all(), [            'first_name' => 'required',            'last_name' => 'required',            'email' => 'required|email|unique:users',            'phone' => 'nullable'        ]);                 if ($validator->fails()) {             Session::flash('error', $validator->messages()->first());             return redirect()->back()->withInput();        }                UserService::createNewUser($request->all());        return redirect()->route('login');     } }

Mire el código ahora: mucho más compacto, fácil de entender. . . y lo más importante, adaptable al cambio. Continuando con nuestra discusión anterior donde teníamos diez tipos diferentes de API, cada uno de ellos ahora llama a una sola función UserService::createNewUser($request->all()); y terminar con eso. Si se necesitan cambios en la lógica de registro de usuarios, el UserService class se encargará de ello mientras que los métodos del controlador no necesitan cambiar en absoluto. Si es necesario configurar la confirmación por SMS después del registro del usuario, UserService se encargará de ello (llamando a otra clase que sepa enviar SMS), y de nuevo los controladores quedan intactos.

Esto es lo que quise decir con enfoque y disciplina: enfoque en el código (una cosa haciendo una sola cosa) y disciplina por parte del desarrollador (no caer en soluciones a corto plazo).

Bueno, ¡fue una gran gira! Y hemos cubierto solo uno de los cinco principios. ¡Vamonos!

“O” is for Open-Closed

Debo decir que a quien se le ocurrió la definición de estos principios ciertamente no estaba pensando en desarrolladores con menos experiencia. Lo mismo ocurre con el Principio Abierto-Cerrado, y los que vendrán van un paso adelante en lo extraño. 😂😂

Independientemente, veamos la definición que todos encontraron para este principio: las clases deben estar abiertas para extensión pero cerradas para modificación. Eh ?? Sí, a mí tampoco me hizo gracia la primera vez que lo encontré, pero con el tiempo he llegado a comprender, y admirar, lo que esta regla está tratando de decir: el código una vez escrito no debería ser necesario cambiarlo.

En un sentido filosófico, esta regla es excelente: si el código no cambia, seguirá siendo predecible y no se introducirán nuevos errores. Pero, ¿cómo es posible siquiera soñar con un código que no cambie cuando todo lo que hacemos como desarrolladores es perseguir los límites del cambio todo el tiempo?

Bueno, en primer lugar, el principio no significa que ni siquiera una línea de código existente pueda cambiar; eso saldría directamente de un país de hadas. El mundo cambia, los negocios cambian y, por lo tanto, el código cambia, no hay forma de evitarlo. Pero lo que sí significa este principio es que restringimos la posibilidad de cambiar el código existente tanto como sea posible. Y también le dice un poco cómo hacerlo: las clases deben estar abiertas para extensión y cerradas para modificación.

"Extensión" aquí significa reutilización, ya sea que la reutilización venga en forma de clases secundarias que heredan la funcionalidad de una clase principal, u otras clases almacenan instancias de una clase y llaman a sus métodos.

Entonces, volviendo a la pregunta del millón de dólares: ¿cómo se escribe código que sobrevive al cambio? Y aquí, me temo, nadie tiene una respuesta clara. En Programación Orientada a Objetos, se han descubierto y refinado varias técnicas para lograr este objetivo, desde estos principios SOLIDOS que estamos estudiando hasta patrones de diseño comunes, patrones empresariales, patrones arquitectónicos y todo eso. No hay una respuesta perfecta, por lo que un desarrollador debe ir cada vez más alto, reuniendo tantas herramientas como pueda y tratando de hacer lo mejor que pueda.

Con eso en mente, veamos una de esas técnicas. Supongamos que necesitamos agregar la funcionalidad para convertir un contenido HTML determinado (¿tal vez una factura?) En un archivo PDF y también forzar una descarga inmediata en el navegador. Supongamos también que tenemos la suscripción paga de un servicio hipotético llamado MilkyWay, que hará la generación real de PDF. Podríamos terminar escribiendo un método de controlador como este:

class InvoiceController extends Controller {     public function generatePDFDownload(Request $request) {         $pdfGenerator = new MilkyWay();         $pdfGenerator->apiKey = env('MILKY_WAY_API_KEY');         $pdfGenerator->setContent($request->content); // HTML format         $pdfFile = $pdfGenerator->generateFile('invoice.pdf');          return response()->download($pdfFile, [             'Content-Type' => 'application/pdf',         ]);     } }

Dejé fuera la validación de solicitudes, etc., para centrarme en el problema central. Notará que este método hace un buen trabajo al seguir el principio de responsabilidad única: no intenta recorrer el contenido HTML que se le pasa y crear un PDF (de hecho, ni siquiera sabe que se le ha dado HTML ); en cambio, transfiere esa responsabilidad a los especialistas MilkyWay class y presenta todo lo que obtiene, como descarga.

Pero hay un pequeño problema.

Nuestro método de controlador depende demasiado de la clase MilkyWay. Si la próxima versión de la API de MilkyWay cambia la interfaz, nuestro método dejará de funcionar. Y si deseamos usar algún otro servicio algún día, tendremos que literalmente hacer una búsqueda global en nuestro editor de código y cambiar todos los fragmentos de código que mencionan MilkyWay. ¿Y por qué es tan malo? Porque aumenta en gran medida la posibilidad de cometer un error y es una carga para la empresa (el tiempo que el desarrollador dedica a solucionar el problema).

Todo este desperdicio porque creamos un método que no estaba cerrado al cambio.

¿Podemos hacerlo mejor?

¡Si podemos!

En este caso, podemos aprovechar una práctica que es algo como esto: programa a interfaces, no implementaciones.

Sí, lo sé, es otro de esos OOPSismos que no tienen sentido la primera vez. Pero lo que está diciendo es que nuestro código debería depender de tipos de las cosas, y no de las cosas particulares en sí mismas. En nuestro caso, necesitamos liberarnos de tener que depender de la MilkyWay clase, y en su lugar dependen de un genérico, un tipo de la clase PDF (todo se aclarará en un segundo).

Ahora bien, ¿qué herramientas tenemos en PHP para crear nuevos tipos? En términos generales, tenemos Herencia e Interfaces. En nuestro caso, crear una clase base para todas las clases de PDF no será una gran idea porque es difícil imaginar diferentes tipos de motores / servicios de PDF compartiendo el mismo comportamiento. Tal vez puedan compartir el setContent() método, pero incluso allí, el proceso de adquisición de contenido podría ser diferente para cada clase de servicio PDF, por lo que escribir todo en una jerarquía de herencia empeorará las cosas.

Con eso entendido, creemos una interfaz que especifique qué métodos queremos que contengan todas nuestras clases de motor PDF:

interface IPDFGenerator {     public function setup(); // API keys, etc.     public function setContent($content);     public function generatePDF($fileName = null); }

¿Entonces que tenemos aqui?

A través de esta interfaz, estamos diciendo que esperamos que todas nuestras clases de PDF tengan al menos esos tres métodos. Ahora, si el servicio que queremos usar (MilkyWay, en nuestro caso) no sigue esta interfaz, es nuestro trabajo escribir una clase que lo haga. Un esbozo de cómo podríamos escribir una clase contenedora para nuestro MilkyWay el servicio es el siguiente:

class MilkyWayPDFGenerator implements IPDFGenerator {     public function __construct() {         $this->setup();     }      public function setup() {         $this->generator = new MilkyWay();         $this->generator->api_key = env('MILKY_WAY_API_KEY');     }      public function setContent($content) {         $this->generator->setContent($content);     }      public function generatePDF($fileName) {         return $this->generator->generateFile($fileName);     } }

Y así, siempre que tengamos un nuevo servicio de PDF, escribiremos una clase contenedora para él. Como resultado, todas esas clases se considerarán de tipo IPDFGenerator.

Entonces, ¿cómo está todo esto conectado con el principio abierto-cerrado y Laravel?

Para llegar a ese punto debemos conocer dos conceptos clave más: los enlaces de contenedor de Laravel y una técnica muy común llamada inyección de dependencia. Nuevamente, palabras grandes, pero la inyección de dependencia simplemente significa que en lugar de crear objetos de clases usted mismo, los menciona en los argumentos de la función y algo los creará automáticamente. Esto le libera de tener que escribir código como $account = new Account(); todo el tiempo y hace que el código sea más comprobable (un tema para otro día). Este "algo" que mencioné toma la forma del Contenedor de servicio en el mundo de Laravel.

Por ahora, piense en ello como algo que puede crear nuevas instancias de clase para nosotros. Veamos cómo ayuda esto.

En el contenedor de servicios de nuestro ejemplo, podemos escribir algo como esto:

$this->app->bind('App\Interfaces\IPDFGenerator', 'App\Services\PDF\MilkyWayPDFGenerator');

Esto es básicamente decir, cada vez que alguien pide un IPDFGenerator, dales el MilkyWayPDFGenerator clase. Y después de toda esa canción y baile, damas y caballeros, llegamos al punto en que todo encaja y ¡el principio abierto-cerrado se revela en acción!

Armados con todo este conocimiento, podemos reescribir nuestro método de controlador de descarga de PDF como este:

class InvoiceController extends Controller {     public function generatePDFDownload(Request $request, IPDFGenerator $generator) {         $generator->setContent($request->content);         $pdfFile = $generator->generatePDF('invoice.pdf');          return response()->download($pdfFile, [             'Content-Type' => 'application/pdf',         ]);     } }

¿Nota la diferencia?

En primer lugar, estamos recibiendo nuestra instancia de clase de generador de PDF en el argumento de la función. Esto lo crea y nos lo pasa el contenedor de servicios, como se explicó anteriormente. El código también es más limpio y no se mencionan las claves de API, etc. Pero, lo más importante, no hay rastro del MilkyWay clase. Esto también tiene el beneficio adicional de hacer que el código sea más fácil de leer (alguien que lo lea por primera vez no dirá, "¡Guau! ¡WTF es esto! MilkyWay?? " y preocuparse constantemente por eso en la parte posterior de su cabeza).

¿Pero el mayor beneficio de todos?

Este método ahora está cerrado para modificaciones y resistente a cambios. Permíteme explicarte. Supongamos que mañana sentimos que el MilkyWay el servicio es demasiado caro (o, como suele suceder, su atención al cliente se ha vuelto una mierda); como resultado, probamos otro servicio llamado SilkyWay y quieres mudarte a él. Todo lo que tenemos que hacer ahora es escribir un nuevo IPDFGenerator clase contenedora para SilkyWay y cambiar el enlace en nuestro código de contenedor de servicio:

$this->app->bind('App\Interfaces\IPDFGenerator', 'App\Services\PDF\SilkyWayPDFGenerator');

¡¡Eso es todo!!

Nada más necesita cambiar, porque nuestra aplicación está escrita de acuerdo con una interfaz (la interfaz IPDFGenerator) en lugar de una clase concreta. El requisito comercial cambió, se agregó un código nuevo (una clase contenedora) y solo se cambió una línea de código; todo lo demás permanece intacto y todo el equipo puede irse a casa con confianza y dormir tranquilo.

¿Quieres dormir tranquilo? ¡Siga el principio abierto-cerrado! 🤭😆

“L” is for Liskov Substitution

Liskov-¿qué ??

Esto suena como algo sacado directamente de un libro de texto de Química Orgánica. Incluso podría hacer que se arrepienta de haber elegido el desarrollo de software como carrera porque pensabas que todo era práctico y nada teórico.

¡Pero detengan sus caballos un segundo! Créame, este principio es tan fácil de entender como intimidante en su nombre. De hecho, podría ser el principio más fácil de entender de los cinco (bueno, err ... si no es el más fácil, al menos tendrá la explicación más corta y sencilla).

Esta regla simplemente dice que el código que funciona con clases principales (o interfaces) no debe romperse cuando esas clases se reemplazan con clases secundarias (o clases de implementación de interfaces). El ejemplo que terminamos justo antes de esta sección es una gran ilustración: si reemplazo el genérico IPDFGenerator escriba el argumento del método con el específico MilkyWayPDFGenerator Por ejemplo, ¿esperaría que el código se rompa o siga funcionando?

¡Sigue trabajando, por supuesto! Eso es porque la diferencia está solo en los nombres, y tanto la interfaz como la clase tienen los mismos métodos que funcionan de la misma manera, por lo que nuestro código funcionará como antes.

Entonces, ¿cuál es el problema con este principio? Bueno, en términos más simples, eso es todo lo que dice este principio: asegúrese de que sus subclases implementen todos los métodos exactamente como se requieren, con el mismo número y tipo de argumentos, y el mismo tipo de retorno. Si incluso un parámetro fuera diferente, sin saberlo, seguiríamos construyendo más código sobre él, y un día tendremos el tipo de desastre apestoso cuya única solución sería eliminarlo.

Ahí. Eso no fue tan malo ahora, ¿verdad? 😇

Se puede decir mucho más sobre la sustitución de Liskov (consulte su teoría y lea sobre Tipos covariantes si realmente se siente valiente), pero en mi opinión, esto es suficiente para el desarrollador promedio que se encuentra con estas misteriosas tierras de patrones y principios por primera vez.

“I” is for Interface Segregation

Segregación de interfaces. . . hmm, eso no suena tan mal, ¿verdad? Parece que tiene algo que ver con la segregación. . . umm, separando. . . interfaces. Me pregunto dónde y cómo.

Si pensaba en este sentido, créame, casi ha terminado de comprender y utilizar este principio. Si los cinco principios SOLID fueran vehículos de inversión, este ofrecería el mayor valor a largo plazo para aprender a codificar bien (está bien, me doy cuenta de que digo eso sobre cada principio, pero ya sabes, entiendes la idea).

Despojado de jerga highfalutin y destilado a su forma más básica, el principio de la Segregación de Interfaces tiene esto que decir: Cuantas más interfaces más numerosas y especializadas haya en su aplicación, más modular y menos extraño será su código.

Veamos un ejemplo muy común y práctico. Cada desarrollador de Laravel se encuentra con el llamado Patrón de repositorio en su carrera, después de lo cual pasan las próximas semanas pasando por una fase cíclica de altibajos y, finalmente, descartan el patrón. ¿Por qué? En todos los tutoriales que cubren el patrón de repositorio, se recomienda crear una interfaz común (llamada repositorio) que definirá los métodos necesarios para acceder o manipular datos. Esta interfaz base podría verse así:

interface IRepository {     public function getOne($id);     public function getAll();     public function create(array $data);     public function update(array $data, $id);     public function delete($id); }

Y ahora, para tu User modelo, se supone que debes crear un UserRepository que implementa esta interfaz; entonces, para tu Customer modelo, se supone que debes crear un CustomerRepository que implementa esta interfaz; entiendes la idea.

Ahora, sucedió en uno de mis proyectos que se suponía que algunos de los modelos no podían ser editados por nadie más que por el sistema. Antes de empezar a poner los ojos en blanco, considere que registrar o mantener una pista de auditoría es un buen ejemplo del mundo real de estos modelos de "solo lectura". El problema al que me enfrenté fue que, dado que se suponía que debía crear repositorios, todos implementaban el IRepository interfaz, dice el LoggingRepository, al menos dos de los métodos de la interfaz, update() y delete() no me sirvieron de nada.

Sí, una solución rápida sería implementarlos de todos modos y dejarlos en blanco o generar una excepción, pero si confiar en tales soluciones de cinta adhesiva estuviera bien para mí, ¡no estaría siguiendo el Patrón de repositorio en primer lugar!

¿Eso significa que todo es culpa del patrón del repositorio?

¡No, en absoluto!

De hecho, un repositorio es un patrón bien conocido y aceptado que aporta consistencia, flexibilidad y abstracción a sus patrones de acceso a datos. El problema es que la interfaz que creamos, o debería decir la interfaz que se popularizó en prácticamente todos los tutoriales, es demasiado amplia.

A veces, esta idea se expresa diciendo que la interfaz es "grasa", pero significa lo mismo: la interfaz hace demasiadas suposiciones y, por lo tanto, agrega métodos que son inútiles para algunas clases, pero aún así esas clases se ven obligadas a implementarlos, lo que resulta en código quebradizo y confuso. Nuestro ejemplo podría haber sido un poco más simple, pero imagina qué lío se puede crear cuando varias clases han implementado métodos que no querían, o aquellos que querían pero faltaban en la interfaz.

La solución es simple y también es el nombre del principio que estamos discutiendo: Segregación de interfaces.

El punto es que no deberíamos crear nuestras interfaces a ciegas. Y tampoco debemos hacer suposiciones, sin importar cuán experimentados o inteligentes creemos que somos. En su lugar, deberíamos crear varias interfaces especializadas más pequeñas, dejando que las clases implementen las que se necesitan y dejando de lado las que no lo son.

En el ejemplo que discutimos, podría haber creado dos interfaces en lugar de una:  IReadOnlyRespository (que contiene las funciones getOne() y getAll()), e IWriteModifyRepository (que contiene el resto de funciones). Para repositorios regulares, diría class UserRepository implements IReadOnlyRepository, IWriteModifyRepository { . .. }. (Nota al margen: aún pueden surgir casos especiales, y eso está bien porque ningún diseño es perfecto. Es posible que incluso desee crear una interfaz separada para cada método, y eso también estará bien, suponiendo que las necesidades de su proyecto sean tan granulares).

Sí, ahora hay más interfaces, y algunos podrían decir que hay demasiado para recordar o que la declaración de clase ahora es demasiado larga (o parece fea), etc., pero mire lo que hemos ganado: especializado, de tamaño pequeño, propio -Interfaces contenidas que pueden combinarse según sea necesario y no se interpondrán entre sí. Mientras escriba software para ganarse la vida, recuerde que es el ideal por el que todos se esfuerzan.

“D” is for Dependency Inversion

Si ha leído las partes anteriores de este artículo, posiblemente sienta que comprende lo que este principio está tratando de decir. Y tendría razón, en el sentido de que este principio es más o menos una repetición de lo que hemos discutido hasta ahora. Su definición formal no da demasiado miedo, así que veámoslo: los módulos de alto nivel no deberían depender de los módulos de bajo nivel; ambos deberían depender de abstracciones.

Sí, tiene sentido. Si tengo una clase de alto nivel (alto nivel en el sentido de que usa otras clases más pequeñas y especializadas para realizar algo y luego tomar algunas decisiones), no deberíamos tener esa clase de alto nivel dependiendo de un nivel bajo en particular. clase de nivel para algún tipo de trabajo. Más bien, deben codificarse para depender de abstracciones (como clases base, interfaces, etc.).

¿Por qué?

Ya vimos un gran ejemplo de ello en la parte anterior de este artículo. Si utilizó un servicio de generación de PDF y su código estaba lleno de new ABCService() clase, el día que la empresa decidiera utilizar algún otro servicio sería el día recordado para siempre, ¡por las razones equivocadas! Más bien, deberíamos usar una forma general de esta dependencia (es decir, crear una interfaz para servicios PDF), y dejar que otra persona maneje su instanciación y nos la pase (en laravel, vimos cómo el Service Container nos ayudó a hacer eso).

Con todo, nuestra clase de alto nivel, que anteriormente tenía el control de la creación de instancias de clase de nivel inferior, ahora tiene que buscar otra cosa. Las tornas han cambiado, y es por eso que llamamos a esto un inversión de dependencias.

Si está buscando un ejemplo práctico, regrese a la parte de este artículo donde discutimos cómo rescatar nuestro código de tener que depender exclusivamente del MilkyWay Clase PDF.

. . .

¡Adivina qué, eso es! Lo sé, lo sé, fue una lectura bastante larga y difícil, y quiero disculparme por eso. Pero mi corazón está con el desarrollador promedio que ha estado haciendo las cosas de manera intuitiva (o de la forma en que se las enseñaron) y no puede entender los principios SOLID. Y he hecho todo lo posible para mantener los ejemplos tan cerca de un Desarrollador de Laravel jornada laboral como sea posible; después de todo, lo que nos sirven son ejemplos que contienen clases de vehículos y automóviles, o incluso genéricos avanzados, reflexión, etc., cuando ninguno de nosotros va a crear bibliotecas de autor.

Si este artículo le resultó útil, deje un comentario. Esto confirmará mi idea de que los desarrolladores realmente luchando por dar sentido a estos conceptos "avanzados", y estaré motivado para escribir sobre otros temas similares. ¡Hasta más tarde! 🙂