Líderes de opinión

Implementación de los principios SOLID en el desarrollo de Android

mm
Añade Unite.AI a tus fuentes preferidas en Google

Escribir software es un acto de creación, y el desarrollo de Android no es la excepción. No se trata solo de hacer que algo funcione. Se trata de diseñar aplicaciones que puedan crecer, adaptarse y seguir siendo manejables con el tiempo.

Como desarrollador de Android que ha enfrentado innumerables desafíos arquitectónicos, he descubierto que adherirse a los principios SOLID puede transformar incluso las bases de código más enredadas en sistemas limpios. Estos no son principios abstractos, sino formas resultantes y reproducibles de escribir código robusto, escalable y mantenible.

Este artículo proporcionará información sobre cómo los principios SOLID pueden aplicarse al desarrollo de Android a través de ejemplos del mundo real, técnicas prácticas y experiencia del equipo de Meta WhatsApp.

Entendiendo los principios SOLID

Los principios SOLID, propuestos por Robert C. Martin, son cinco principios de diseño para la programación orientada a objetos que garantizan una arquitectura de software limpia y eficiente.

  • Principio de responsabilidad única (SRP): Una clase debe tener una y solo una razón para cambiar.
  • Principio abierto/cerrado (OCP): Las entidades de software deben estar abiertas para la extensión pero cerradas para la modificación.
  • Principio de sustitución de Liskov (LSP): Los subtipos deben ser sustituibles por sus tipos base.
  • Principio de segregación de interfaces (ISP): Las interfaces deben ser específicas del cliente y no deben obligar a la implementación de métodos no utilizados.
  • Principio de inversión de dependencias (DIP): Los módulos de alto nivel deben depender de abstracciones, no de módulos de bajo nivel.

Al integrar estos principios en el desarrollo de Android, podemos crear aplicaciones que sean más fáciles de escalar, probar y mantener.

Principio de responsabilidad única (SRP): Simplificando responsabilidades

El Principio de responsabilidad única es la base para escribir código mantenible. Establece que cada clase debe tener una sola preocupación de la que se hace responsable. Un anti-patrón común es considerar las Actividades o Fragmentos como “clases Dios” que manejan responsabilidades que van desde la representación de la interfaz de usuario, la recuperación de datos, el manejo de errores, etc. Este enfoque convierte las pruebas y el mantenimiento en una pesadilla.

Con el SRP, separamos diferentes preocupaciones en componentes diferentes: por ejemplo, en una aplicación de noticias, creamos o leemos noticias.


class NewsRepository {
fun fetchNews(): List {
// Maneja la lógica de recuperación de datos
}
}

class NewsViewModel(private val newsRepository: NewsRepository) { fun loadNews(): LiveData<List> { // Maneja el estado de la interfaz de usuario y el flujo de datos } }

class NewsActivity : AppCompatActivity() { // Maneja solo la representación de la interfaz de usuario }

 

Cada clase tiene solo una responsabilidad; por lo tanto, es fácil probar y modificar sin tener efectos secundarios.

En el desarrollo de Android moderno, el SRP se implementa principalmente junto con la arquitectura recomendada que utiliza Jetpack. Por ejemplo, la lógica relacionada con la manipulación de datos podría residir dentro de ViewModel, mientras que las Actividades o Fragmentos solo deben preocuparse por la interfaz de usuario y las interacciones. La recuperación de datos podría delegarse a algún repositorio separado, ya sea desde bases de datos locales como Room o capas de red como Retrofit. Esto reduce el riesgo de inflar las clases de interfaz de usuario, ya que cada componente obtiene solo una responsabilidad. Al mismo tiempo, su código será mucho más fácil de probar y mantener.

Principio abierto/cerrado (OCP): Diseñando para la extensión

El Principio abierto/cerrado declara que una clase debe estar abierta para la extensión pero no para la modificación. Es más razonable para las aplicaciones de Android, ya que constantemente se actualizan y agregan nuevas características.

El mejor ejemplo de cómo utilizar el principio OCP en aplicaciones de Android es mediante interfaces y clases abstractas. Por ejemplo:


interface PaymentMethod {
fun processPayment(amount: Double)
}

class CreditCardPayment : PaymentMethod { override fun processPayment(amount: Double) { // Implementación para pagos con tarjeta de crédito } }

class PayPalPayment : PaymentMethod { override fun processPayment(amount: Double) { // Implementación para pagos con PayPal } }

 

Agregar nuevos métodos de pago no requiere cambios en las clases existentes; requiere crear nuevas clases. Aquí es donde el sistema se vuelve flexible y puede escalarse.

En aplicaciones creadas para dispositivos Android, el Principio abierto/cerrado es muy útil cuando se trata de conmutadores de características y configuraciones tomadas dinámicamente. Por ejemplo, si su aplicación tiene una interfaz base de AnalyticsTracker que informa eventos a diferentes servicios de análisis, Firebase y Mixpanel y rastreadores personalizados internos, cada nuevo servicio puede agregarse como una clase separada sin cambios en el código existente. Esto mantiene su módulo de análisis abierto para la extensión: puede agregar nuevos rastreadores, pero cerrado para la modificación: no reescribe las clases existentes cada vez que agrega un nuevo servicio.

Principio de sustitución de Liskov (LSP): Asegurando la intercambiabilidad

El Principio de sustitución de Liskov establece que los subtipos deben ser sustituibles por sus tipos base, y el comportamiento de la aplicación no debe cambiar. En Android, este principio es fundamental para diseñar componentes reutilizables y predecibles.

Por ejemplo, una aplicación de dibujo:


abstract class Shape {
abstract fun calculateArea(): Double
}

class Rectangle(private val width: Double, private val height: Double) : Shape() { override fun calculateArea() = width * height }

class Circle(private val radius: Double) : Shape() { override fun calculateArea() = Math.PI * radius * radius }

 

Tanto Rectangle como Circle pueden reemplazarse entre sí de manera intercambiable sin que el sistema falle, lo que significa que el sistema es flexible y sigue el LSP.

Considere las subclases de RecyclerView.Adapter de Android. Cada subclase del adaptador se extiende desde RecyclerView.Adapter<VH> y anula las funciones principales como onCreateViewHolder, onBindViewHolder y getItemCount. El RecyclerView puede utilizar cualquier subclase de manera intercambiable siempre que esos métodos se implementen correctamente y no rompan la funcionalidad de su aplicación. Aquí, el LSP se mantiene, y su RecyclerView puede ser flexible para sustituir cualquier subclase del adaptador a voluntad.

Principio de segregación de interfaces (ISP): Interfaces enfocadas y delgadas

En aplicaciones más grandes, es común definir interfaces con demasiada responsabilidad, especialmente alrededor de la red o el almacenamiento de datos. En su lugar, divídalas en interfaces más pequeñas y enfocadas. Por ejemplo, una interfaz ApiAuth responsable de los puntos de conexión de autenticación de usuario debe ser diferente de una interfaz ApiPosts responsable de publicaciones de blog o feeds sociales. Esta separación evitará que los clientes que necesitan solo los métodos relacionados con las publicaciones se vean obligados a depender de e implementar llamadas de autenticación, manteniendo así su código, así como la cobertura de pruebas, más delgada.

El Principio de segregación de interfaces significa que en lugar de tener interfaces grandes, se deben utilizar varias interfaces más pequeñas y enfocadas. El principio evita situaciones en las que las clases implementan métodos innecesarios.

Por ejemplo, en lugar de tener una sola interfaz grande que represente las acciones de los usuarios, considere el código en Kotlin:


interface Authentication {
fun login()
fun logout()
}

interface ProfileManagement { fun updateProfile() fun deleteAccount() }

 

Las clases que implementan estas interfaces pueden centrarse solo en la funcionalidad que requieren, limpiando así el código y haciéndolo más mantenible.

Principio de inversión de dependencias (DIP): Abstrayendo dependencias

El Principio de inversión de dependencias promueve la desacoplación asegurando que los módulos de alto nivel dependan de abstracciones en lugar de implementaciones concretas. Este principio se alinea perfectamente con las prácticas de desarrollo modernas de Android, especialmente con frameworks de inyección de dependencias como Dagger y Hilt.

Por ejemplo:


class UserRepository @Inject constructor(private val apiService: ApiService) {
fun fetchUserData() {
// Recupera los datos de usuario desde una abstracción
}
}

 

Aquí, UserRepository depende de la abstracción ApiService, haciéndolo flexible y probado. Este enfoque permite reemplazar la implementación, como usar un servicio de simulación durante las pruebas.

Frameworks como Hilt, Dagger y Koin facilitan la inyección de dependencias proporcionando una forma de suministrar dependencias a los componentes de Android, eliminando la necesidad de instanciarlos directamente. En un repositorio, por ejemplo, en lugar de instanciar una implementación de Retrofit, inyectaría una abstracción, por ejemplo, una interfaz ApiService. De esta manera, podría cambiar fácilmente la implementación de la red, por ejemplo, un servicio de simulación en memoria para pruebas locales, y no necesitaría cambiar nada en su código del repositorio. En aplicaciones reales, puede encontrar que las clases están anotadas con @Inject o @Provides para proporcionar estas abstracciones, lo que hace que su aplicación sea modular y amigable con las pruebas.

Beneficios prácticos de los principios SOLID

Adoptar los principios SOLID en el desarrollo de Android produce beneficios tangibles:

  1. Mejora de la probabilidad: Las clases y las interfaces enfocadas facilitan la escritura de pruebas unitarias.
  2. Mejora de la mantenibilidad: La separación clara de las preocupaciones simplifica la depuración y las actualizaciones.
  3. Escalabilidad: Los diseños modulares permiten la adición de características de manera fluida.
  4. Colaboración: El código bien estructurado facilita el trabajo en equipo y reduce el tiempo de incorporación de nuevos desarrolladores.
  5. Optimización del rendimiento: Las arquitecturas eficientes minimizan el procesamiento y el uso de memoria innecesarios.

Aplicaciones en el mundo real

En aplicaciones ricas en características, como aplicaciones de comercio electrónico o redes sociales, la aplicación de los principios SOLID puede reducir significativamente el riesgo de regresiones cada vez que se agrega una nueva característica o servicio. Por ejemplo, si un nuevo requisito requiere un flujo de compra en la aplicación, puede introducir un módulo separado que implemente las interfaces necesarias (Pago, Análisis) sin tocar los módulos existentes. Este tipo de enfoque modular, impulsado por SOLID, permite que su aplicación de Android se adapte rápidamente a las demandas del mercado y mantiene la base de código alejada de convertirse en espaguetis con el tiempo.

Al trabajar en un proyecto grande que requiere la colaboración de muchos desarrolladores, es altamente recomendable mantener una base de código compleja con los principios SOLID. Por ejemplo, separar la recuperación de datos, la lógica de negocio y el manejo de la interfaz de usuario en el módulo de chat ayudó a reducir la posibilidad de regresiones mientras se escalaba el código con nuevas características. De manera similar, la aplicación del DIP fue crucial para abstraer las operaciones de red, lo que permitió cambiar entre clientes de red con casi ninguna interrupción.

Conclusión

Más que una guía teórica, los principios de SOLID son en realidad la filosofía práctica para crear software resistente, adaptable y mantenible. En el mundo en constante movimiento del desarrollo de Android, con requisitos que cambian casi tan a menudo como las tecnologías, la adhesión a estos principios proporciona un terreno firme sobre el que se puede fundamentar el éxito.

Un buen código no se trata solo de hacer que algo funcione; se trata de crear un sistema que pueda seguir funcionando y creciendo con las necesidades en evolución. Al abrazar los principios SOLID, no solo escribirá un mejor código, sino que también construirá aplicaciones que son un placer desarrollar, escalar y mantener.

Farhana es una desarrolladora de aplicaciones móviles experta que ha entregado con éxito numerosas aplicaciones móviles desde cero. Ha realizado capacitación en desarrollo de Android para funcionarios de ICT del gobierno en Bhután, ha mentorizado a muchos nuevos miembros y ha liderado equipos para lograr el éxito juntos.