S.O.L.I.D Los primeros 5 principios del Diseño Orientado a Objetos con Dart

CarlosMillan
Mar 1, 2019 · 7 min read

Esta es una traducción del artículo escrito por el compañero Pawan Kumar sobre los principios SOLID, el cual puede encontrar el post original aquí.

Su canal de YouTube, Github y Facebook.

S.O.L.I.D es un acrónimo de los primeros cinco principios de diseño orientado a objetos(DOO) por Robert C. Martin, popularmente conocido como Uncle Bob.

Estos principios, cuando se combinan, facilitan que un programador desarrolle un software que sea fácil de mantener y extender. También facilitan a los desarrolladores evitar husmear el código, refaccionan fácilmente el código y también forman parte del desarrollo de software ágil o adaptativo.

Esto es solo un simple artículo de “bienvenido a S.O.L.I.D.”, simplemente arroja luz sobre lo que S.O.L.I.D. es.

S.O.L.I.D. REPRESENTA:

  • S — Single responsibility principle (Principio de responsabilidad única)
  • O — Open closed principle (Principio cerrado abierto)
  • L — Liskov substitution principle (Principio de sustitución Liskov)
  • I — Interface segregation principle (Principio de segregación de interfaz)
  • D — Dependency Inversion principle (Principio de inversión de dependencia)

# Principio de responsabilidad única

Una clase debe tener una y solo una razón para cambiar, lo que significa que una clase debe tener solo un trabajo.

Por ejemplo, digamos que tenemos algunas formas y queríamos sumar todas las áreas de las formas. Bueno, esto es bastante simple, ¿verdad?

Primero, creamos nuestras clases de formas y los constructores configuran los parámetros requeridos. A continuación, continuamos creando la clase AreaCalculator y luego escribimos nuestra lógica, para resumir, las áreas de todas las formas proporcionadas.

Para usar la clase AreaCalculator, simplemente creamos una instancia de la clase y pasamos en una lista de formas, y mostramos el resultado en la parte inferior de la página.

El problema con el método de salida es que el AreaCalculator maneja la lógica para generar los datos. Por lo tanto, ¿qué sucede si el usuario desea generar los datos como json o HTML o algo más?

Toda esa lógica sería manejada por la clase AreaCalculator, esto es contra lo que SRP frunce el ceño; La clase AreaCalculator solo debe sumar las áreas de las formas proporcionadas, no debería importar si el usuario desea json o HTML.

Por lo tanto, para solucionar este problema, puede crear una clase SumCalcOutputter y usarla para manejar cualquier lógica que necesite para controlar cómo se muestran las áreas de suma de todas las formas proporcionadas.

La clase SumCalcOutputter funcionaría así:

Ahora, cualquier clase de lógica que necesite para enviar los datos al usuario ahora es manejada por la clase SumCalcOutputter.

# Principio Abierto Cerrado

Los objetos o entidades deben estar abiertos por extensión, pero cerrados por modificación.

Esto simplemente significa que una clase debe ser fácilmente ampliable sin modificar la clase en sí. Echemos un vistazo a la clase AreaCalculator, especialmente su método de suma.

Si quisiéramos que el método de suma sea capaz de sumar las áreas de más formas, tendríamos que agregar más bloques if / else y eso va en contra del principio Abierto-cerrado.

Hay una manera de mejorar este método de suma es eliminar la lógica para calcular el área de cada forma fuera del método de suma y adjuntarla a la clase de la forma.

Ahora, calcular la suma de cualquier forma provista debería ser tan simple como:

Ahora podemos crear otra clase de forma y pasarla al calcular la suma sin romper nuestro código. Sin embargo, ahora surge otro problema, ¿cómo sabemos que el objeto pasado al AreaCalculator es en realidad una “forma” o si la forma tiene un método llamado área?

La codificación a una interfaz es una parte integral de S.O.L.I.D, un ejemplo rápido es que creamos una interfaz (clase abstracta en dart que se implementa), que implementa cada forma:

En nuestro método de suma AreaCalculator podemos verificar si las formas proporcionadas son en realidad instancias de ShapeInterface, de lo contrario, lanzamos una excepción:

# Principio de sustitución Liskov

Sea q (x) una propiedad demostrable sobre objetos de x de tipo T. Entonces q (y) debería ser demostrable para objetos y de tipo S donde S es un subtipo de T.

Todo lo que se indica es que cada subclase / clase derivada debe ser sustituible por su clase base / padre.

Aún haciendo uso de nuestra clase AreaCalculator, digamos que tenemos una clase VolumeCalculator que extiende la clase AreaCalculator:

En la clase SumCalculatorOutputter:

Si intentamos ejecutar un ejemplo como este:

El programa no retumba, pero cuando llamamos al método HTML en el objeto $ output2, obtenemos un error que nos informa de una lista para la conversión de cadenas.

Para solucionar este problema, en lugar de devolver una matriz desde el método de suma de clase de VolumeCalculator, simplemente debe:

Los datos sumados como un flotante, doble o entero.

# Principio de segregación de interfaz

Un cliente nunca debe ser obligado a implementar una interfaz que no use o los clientes no deben ser obligados a depender de los métodos que no usan.

Aún usando nuestro ejemplo de formas, sabemos que también tenemos formas sólidas, por lo que, como también quisiéramos calcular el volumen de la forma, podemos agregar otro contrato a la ShapeInterface:

Cualquier forma que creamos debe implementar el método de volumen, pero sabemos que los cuadrados son formas planas y que no tienen volúmenes, por lo que esta interfaz obligaría a la clase Square a implementar un método que no tiene uso.

El ISP dice que no a esto, en su lugar, podría crear otra interfaz llamada SolidShapeInterface que tenga el contrato de volumen y formas sólidas como cubos e.t.c puede implementar esta interfaz:

Este es un enfoque mucho mejor, pero un error que debe tener en cuenta es cuando insinúa estas interfaces, en lugar de usar un ShapeInterface o un SolidShapeInterface.

Puede crear otra interfaz, tal vez ManageShapeInterface, e implementarla en formas planas y sólidas, de esta manera puede ver fácilmente que tiene una única API para administrar las formas. Por ejemplo:

Ahora, en la clase AreaCalculator, podemos reemplazar fácilmente la llamada al método del área con calcular y también verificar si el objeto es una instancia de ManageShapeInterface y no la ShapeInterface.

# Principio de inversión de dependencia

Lo último, pero definitivamente no lo menos, establece que:

Las entidades deben depender de abstracciones, no de concreciones. Establece que el módulo de alto nivel no debe depender del módulo de bajo nivel, sino que debe depender de abstracciones.

Esto puede sonar difícil, pero es muy fácil de entender. Este principio permite el desacoplamiento, un ejemplo que parece ser la mejor manera de explicar este principio:

En primer lugar, MySQLConnection es el módulo de bajo nivel, mientras que el PasswordReminder es alto, pero de acuerdo con la definición de D en S.O.L.I.D. que establece que Dependiendo de la abstracción no de las concreciones, este fragmento de código anterior viola este principio, ya que la clase PasswordReminder se ve obligada a depender de la clase MySQLConnection.

Más adelante, si cambiara el motor de la base de datos, también tendría que editar la clase PasswordReminder y, por lo tanto, violaría el principio de apertura-cierre.

A la clase PasswordReminder no le debe importar qué base de datos utiliza su aplicación, para solucionar esto nuevamente, “codificamos a una interfaz”, ya que los módulos de alto y bajo nivel deben depender de la abstracción, podemos crear una interfaz:

La interfaz tiene un método de conexión y la clase MySQLConnection implementa esta interfaz, también en lugar de directamente la clase MySQLConnection en el constructor del PasswordReminder, en su lugar, tipeamos la interfaz y no importa el tipo de base de datos que usa su aplicación, el PasswordReminder la clase puede conectarse fácilmente a la base de datos sin ningún problema y no se infringe el OCP.

According to the little snippet above, you can now see that both the high-level and low-level modules depend on abstraction.

# Conclusion

# Ejemplos completos de código

CarlosMillan

Written by

Dart / Flutter developer and space lover.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade