SOLID: Principio de segregación de interfaz o Interface segregation principle

En este artículo hablamos del cuarto principio de los principios de programación SOLID.

Significado

Este principio establece que las clases clientes de otras no deberían verse forzados a depender de interfaces que no usan. En su lugar se apoya la definición de interfaces más específicas para cada caso.

Cuando hablamos de interfaces estamos hablando del concepto de interfaz de clase del lenguaje de programación Java así como la adaptación a cualquier otro tipo de lenguaje de programación. Por ejemplo en Swift estaríamos hablando de protocolos.

En pocas palabras una interface es un acuerdo entre las clases que implementan la interfaz y la propia interfaz definida con sus propiedades y funciones. El acuerdo consiste en que cada clase que implemente dicha interfaz deberá codificar cada una de las propiedades y funciones definidas en la interfaz.

Ejemplo

Este principio es más sencillo de entender con un ejemplo.

Imaginemos que estamos modelando una interfaz de acciones para distintas clases de aves.

 

interface Ave {

    funcion comer()

    funcion cantar()

    funcion volar()

}

 

class Loro implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Aguila implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        noHacerNada()

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Gallina implements Ave {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        noHacerNada()

    }

}

 

En este ejemplo tenemos una interfaz llamada Ave con 3 acciones pero no todas las aves realizan esas tres acciones. Por ejemplo las águilas no cantan y las gallinas no vuelan. Pero si una clase implementa una interfaz está obligada a incluir esas funciones aunque no hagan nada.

Solución

La solución consiste en segregar la interfaz Ave en 3 interfaces más específicas.

 

interface Ave {

    funcion comer()

}

 

interface AveCantora {

    funcion cantar()

} 

 

interface AveVoladora {

    funcion volar()

}

 

class Loro implements Ave, AveCantora, AveVoladora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Aguila implements Ave, AveVoladora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion volar() {

        // código para volar

    }

}

 

class Gallina implements Ave, AveCantora {

    propiedad nombre

    propiedad color

    propiedad tamaño

 

    funcion comer() {

        // código para comer

    }

 

    funcion cantar() {

        // código para cantar

    }

}

 

Ahora cada clase sólo implementa las interfaces necesarias a sus capacidades por lo que no hay funciones que no hacen nada.

Con esta solución es sencillo incluir en un futuro por ejemplo aves que puedan nadar y cada clase sólo implementará las funciones necesarias.

Además con esta solución se mejora aún más el principio de responsabilidad única.

SOLID: Principio de substitución de Liskov o Liskov substitution principle

Este es el tercer principio de los principios de programación SOLID y se relaciona con una característica de la programación orientada a objetos: la Herencia.

Significado

El principio Declara que una subclase debe ser sustituible por su superclase.

Si en nuestro programa al hacer esto la aplicación falla, estaremos incumpliendo este principio SOLID.

Cumpliendo con este principio se confirmará que nuestro programa tiene una jerarquía de clases fácil de entender y un código reutilizable.

Ejemplo

Siguiendo con los ejemplos de los artículos de Principio de responsabilidad única y el Principio de abierto/cerrado imaginemos que queremos controlar si una persona puede acceder a una sala restringida sólo a ciertos trabajadores. En nuestro ejemplo tendremos operarios, técnicos, estudiantes, jefes y guardias y sólo los guardias y los jefes podrán acceder a la sala de control ya que estas dos clases incluyen la función entraEnSalaDeControl(). Realizaremos esta gestión en la clase ControlarAcceso que contiene una función para imprimir si tiene acceso o no a la sala.

A esta función se le pasa como parámetro(array) una lista de objetos de la clase Persona.

Veamos el código.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

    funcion calculaEdad()
}

 

class Operario: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion tomaUnDescanso()

}

 

class Técnico: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion revisaOperario()

    funcion controlaCalidad()

    funcion tomaUnDescanso()

}

 

class Jefe: Persona {
    propiedad departamento

    funcion revisaPersona()

    funcion controlaCalidad()

    funcion entraEnSalaDeControl() {

        imprime(“Jefe.nombre accede a la sala de control»)

 

    }

}

 

class Guardia: Persona {
    propiedad numeroDePlaca

    funcion vigilaSala()

    funcion arrestaPersona()

    funcion entraEnSalaDeControl() {

        imprime(“Guardia.nombre accede a la sala de control»)

    }
}

 

class ControlarAcceso {

    funcion imprimePermisos(listaDePersonas: Array de Persona) {

        Bucle personaDeLaLista en listaDePersonas {

            Si personaDeLaLista esInstanciaDe Operario

            ENTONCES imprime(“personaDeLaLista.nombre no tiene acceso.»)

            Si personaDeLaLista esInstanciaDe Técnico

            ENTONCES imprime(“personaDeLaLista.nombre no tiene acceso.»)

            Si personaDeLaLista esInstanciaDe Guardia

            ENTONCES personaDeLaLista.entraEnSalaDeControl()

            Si personaDeLaLista esInstanciaDe Jefe

            ENTONCES personaDeLaLista.entraEnSalaDeControl()

        }

    }

}

 

Este código además de incumplir el principio de abierto / cerrado incumple el principio de substitución de Liskov.

Solución

Utilizando las propiedades de la Herencia en programación orientada a objetos podemos incluir una función entraEnSalaDeControl() con un valor por defecto de no acceso en la clase Persona. 

Todas las clases hijas heredarán esta función por lo que sólo habría que sobreescribir esta función en las clases que si tengan acceso.

El código quedaría de la siguiente forma.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

    funcion calculaEdad()

    funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre no tiene acceso a la sala de control»)

    }

}

 

class Operario: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion tomaUnDescanso()

}

 

class Técnico: Persona {
    propiedad numeroDeIdentificación

    funcion utilizaHerramienta()

    funcion revisaOperario()

    funcion controlaCalidad()

    funcion tomaUnDescanso()

}

 

class Jefe: Persona {
    propiedad departamento

    funcion revisaPersona()

    funcion controlaCalidad()

    SobreEscrito funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre accede a la sala de control»)

    }

}

 

class Guardia: Persona {
    propiedad numeroDePlaca

    funcion vigilaSala()

    funcion arrestaPersona()

    SobreEscribe funcion entraEnSalaDeControl() {

        imprime(“Persona.nombre accede a la sala de control»)

    }
}

 

class ControlarAcceso {

    funcion imprimePermisos(listaDePersonas: Array de Persona) {

        Bucle personaDeLaLista en listaDePersonas {

            personaDeLaLista.entraEnSalaDeControl

        }

    }

}

 

Ahora la función imprimePermisos no tiene que consultar a qué clase pertenece la persona de la lista ya que por contrato de herencia todas las clases hijas de Persona incluyen la función entraEnSalaDeControl(). Esto facilita que si en el futuro es necesario agregar nuevas clases hijas de Persona sólo habrá que sobreescribir la función en aquellas clases que si tengan acceso. De esta forma mejoramos el mantenimiento del proyecto.

iOS desde cero en Twitch

Cada vez es más palpable la necesidad de conocimientos en programación en esta sociedad y es cada vez más habitual ver que la forma de compartir y divulgar conocimientos en Internet es a través de video.

Un buen formador y divulgador se adapta a los tiempos y Diego Freniche no iba a ser menos.

Uniendo la programación en Swift, la divulgación sobre tecnología y la forma de enseñar conceptos de desarrollo de software de Diego nace el proyecto iOS desde cero en Twitch.

Gracias a este proyecto tenemos una forma divertida y asequible de acercarnos a la programación en iOS.

La retransmisión de los vídeos en directo se realiza a través de la plataforma Twitch aunque los vídeos permanecerán disponibles en el canal de Youtube de Diego Freniche por si es necesario volver a consultar algo o no puedes asistir a algún directo.

Todo el código publicado así como la información del proyecto sobre horarios, canales y peticiones se gestionan en el repositorio de iOS desde cero en Github.

SOLID: Principio de abierto/cerrado u Open/closed principle

Como vimos en el artículo de los principios SOLID este es el segundo principio.

Significado

Este principio Establece que las clases deben estar abiertas para su extensión, pero cerradas para su modificación.

Ejemplo

Siguiendo con el ejemplo de la clase Persona que vimos en el artículo del principio de responsabilidad única imaginemos que tenemos una lista de personas(array) y queremos imprimir por pantalla las personas que tengan la edad suficiente para conducir un vehículo. Pero debemos tener en cuenta que según el país esta edad puede ser distinta. Para completar este programa crearemos una clase ListaDePersonasMayoresDeEdad que resolverá este problema con la función imprimeConductores().

Para el ejemplo de código en lugar de cargar los datos de personas de la base de datos tendremos una lista que se rellenará en el constructor de la clase ListaDePersonasMayoresDeEdad.

El código de ejemplo quedaría así:

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

funcion calculaEdad()
}

 

class ListaDePersonasMayoresDeEdad {

    propiedad listaDePersonas

 

    funcion Constructor() {

        listaDePersonas = [

            Persona(“Fulano”, “De tal”, Español, 1/5/1981),

            Persona(“Mengano”, “De cual”, Mejicano, 3/12/1992),

            Persona(“John”, “Doe”, Norteamericano, 13/2/2012),

            Persona(“Boris”, “Stalin”, Ruso, 20/9/1999)

        ]

    }

 

    funcion imprimeConductores() {

        bucle personaDeLaLista en listaDePersonas {

            Si personaDeLaLista.nacionalidad = Español Y personaDeLaLista.calculaEdad() >= 18

            entonces imprime(personaDeLaLista)

            Si personaDeLaLista.nacionalidad = Norteamericano Y personaDeLaLista.calculaEdad() >= 16

            entonces imprime(personaDeLaLista)

            Si personaDeLaLista.nacionalidad = Ruso Y personaDeLaLista.calculaEdad() >= 18

            entonces imprime(personaDeLaLista)

        }

    }

}

 

Este programa no cumple el principio de abierto/cerrado por la sencilla razón de la necesidad de modificar la clase ListaDePersonasMayoresDeEdad cada vez que haya una nacionalidad nueva en nuestra base de datos. La función imprimeConductores() tendría que ser modificada cada vez que en la base de datos apareciese un usuario con una nueva nacionalidad.

Solución

Dependiendo de las características y las capacidades del lenguaje de programación que estemos utilizando la solución puede crearse mediante clases abstractas, protocolos o empleando una solución generalista como la creación de una clase nacionalidad que encapsule ciertos requisitos en la base de datos.

El objetivo de la solución se enfoca en evitar tener que modificar una función o clase en el futuro simplemente porque se agreguen nuevos datos a la base de datos.

Recordemos que este principio nos mueve a extender una clase para evitar estar modificando otra constantemente. Por esa razón podemos extender la clase Persona gracias a la nueva clase Nacionalidad

Veamos el código de esta solución.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad

    propiedad fechaDeNacimiento

funcion calculaEdad()
}

 

class Nacionalidad {

    propiedad nombreDeNacionalidad

    propiedad edadParaConducir

}

 

class ListaDePersonasMayoresDeEdad {

    propiedad listaDePersonas

    propiedad nacionalidades 

    funcion Constructor() {

        nacionalidades = [

            Nacionalidad(Español , 18),

            Nacionalidad(Norteamericano , 21),

            Nacionalidad(Mejicano , 15),

            Nacionalidad(Ruso , 18),

        ]

 

        listaDePersonas = [

            Persona(“Fulano”, “De tal”, Español, 1/5/1981),

            Persona(“Mengano”, “De cual”, Mejicano, 3/12/1992),

            Persona(“John”, “Doe”, Norteamericano, 13/2/2012),

            Persona(“Boris”, “Stalin”, Ruso, 20/9/1999)

        ]

    }

 

    funcion imprimeConductores() {

        bucle personaDeLaLista en listaDePersonas {

            Si personaDeLaLista.calculaEdad() >= personaDeLaLista.nacionalidad.edadParaConducir

            entonces imprime(personaDeLaLista)

        }

    }

 

}

 

Ahora el bucle que hay en la función imprimeConductores() se ha reducido y es más legible.

Esta solución es más fácil de mantener y facilita que la clase Nacionalidad pueda incorporar más información interesante para nuestra aplicación y su mantenimiento ha mejorado al no existir la necesidad de modificar eternamente una función de una clase concreta dependiendo de qué datos hay en la base de datos.

Participación en conversaciones en directo con personas inspiradoras

Los chicos de Más allá de la marca personal están realizando un proyecto en el que cada semana presentan dos entrevistas con personas que consideran inspiradoras.

Emprendedores, escritores, coaches, marketers, CEOs, psicólogos, vendedores, inversores, deportistas… 

Estas entrevistas ofrecen la oportunidad de aprender con sus experiencias personales y profesionales y descubrir cómo las gestionaron.

Mi participación

Imagen con información del evento

El próximo 1 de febrero de 2022 participaré en directo en una de estas entrevistas para compartir mis experiencias, mis opiniones y mis ideas.

Puedes suscribirte y asistir al evento en el enlace al evento del 1 de febrero.

SOLID: Principio de responsabilidad única o Single responsibility principle

Como vimos en el artículo de los principios SOLID este es el primero de los principio SOLID.

Significado

Este principio establece que una clase o componente debe ser responsable de una sola cosa.

Este principio está alineado con el término de desacoplamiento o decoupled utilizado en muchas metodologías de desarrollo.

Cuando se codifica una clase que sólo posee una responsabilidad o función es fácil diseñar tests para comprobar que funciona correctamente y además facilita que esa clase pueda ser reutilizada en futuros proyectos.

Ejemplo

Imaginemos el siguiente ejemplo en el que tenemos una clase Persona que contiene los datos de un usuario.

El código de nuestra clase podría ser algo como:

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad
    propiedad fechaDeNacimiento

funcion calculaEdad()
    funcion guardaEnBaseDeDatos()
    funcion cargaDesdeBaseDeDatos()
}

Este ejemplo viola el principio de responsabilidad única.

Esto se debe a que la clase Persona además de guardar la información de una persona también se encarga de almacenar y recuperar la información de una base de datos.

Imaginemos que nuestra aplicación está utilizando MySQL pero en una futura versión queremos incluir soporte para la base de datos RealM manteniendo la opción de MySQL. Los cambios que tendríamos que hacer a nuestra clase persona complicarían enormemente el código.

En cambio si creamos una clase Persona que almacene los datos de un usuario y otra clase PersonaMySQL que se encargue de recuperar y almacenar los datos en nuestra base de datos MySQL permitiría fácilmente la codificación de una nueva clase PersonaRealM que haga lo mismo que la clase PersonaMySQL pero adaptando el código a la base de datos RealM.

 

class Persona {
    propiedad nombre
    propiedad apellidos
    propiedad nacionalidad
    propiedad fechaDeNacimiento

funcion calculaEdad()
}

class PersonaMySQL {
    funcion guardaEnBaseDeDatos()
    funcion cargaDesdeBaseDeDatos()
}

class PersonaRealM {
    funcion guardaEnBaseDeDatos()
    funcion cargaDesdeBaseDeDatos()
}

Nuestro programa será mucho más cohesivo y estará más encapsulado aplicando este principio. Siguiendo esta forma de agrupar funciones y propiedades en nuestras clases se simplifica las operaciones de mantenimiento como localizar dónde hay un error o ampliar la funcionalidad de nuestra aplicación agregando soporte a otras bases de datos.