Zoltan Tasi en Unsplash"> Zoltan Tasi en Unsplash"> Zoltan Tasi en Unsplash">

Foto de <a href="https://unsplash.com/es/@zoltantasi?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Zoltan Tasi</a> en <a href="https://unsplash.com/es/fotos/formacion-rocosa-marron-rodeada-de-hierba-verde-QxjEi8Fs9Hg?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>

Principios SOLID

Sí, este es otro post sobre los principios SOLID, pero esta vez quiero mostrarte cómo aplicarlos en tu trabajo diario y cómo identificar cuándo necesitas aplicarlos.

Contexto e Intención

Cada día, cada nuevo proyecto, me encuentro en la misma situación: necesito desarrollar una nueva funcionalidad, o mantener una existente en un código legacy. Todo bien hasta que creo un pull request. Entonces me encuentro tratando de defender mis cambios de código justificando que estoy siguiendo buenas prácticas, principios SOLID, etc.

¿Por qué necesito explicar buenas prácticas de código a ingenieros que se supone que las conocen? Las conocen, en teoría, ¿verdad? Bueno, veamos.

El Problema

Antes de explicar los principios SOLID, veamos un ejemplo real de un código que no los sigue.

Usaré un ejemplo real de un código legacy en el que trabajé con algunas adaptaciones debido a restricciones de NDA.

Empecemos definiendo la arquitectura de la app: Una aplicación móvil simple con múltiples activities y fragments, cada uno con su propia lógica y gestión de estado. Cada activity podría depender de su propio view model para gestionar el estado y la lógica de negocio. También, cada fragment podría depender de su propio view model. Ya empezamos a ver algunos problemas aquí, ¿verdad? bueno, podemos discutir eso más adelante.

Si te pregunto, ¿de qué es responsable el Activity/Fragment? podrías decir que su responsabilidad es gestionar la UI y la lógica de negocio. Incorrecto. No es responsable de gestionar la lógica de negocio. Es responsable de gestionar la UI. Entonces, ¿por qué estás usando el Activity/Fragment para gestionar la lógica de negocio?

// com.jesusdmedinac.app.activities.MyActivity
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        viewModel.someState.observe(this, Observer { state ->
            // Actualizar UI basado en el estado
        })

        // Lógica de negocio
        viewModel.doSomething()

        // Más lógica de negocio
        viewModel.someManager.doSomething()

        // Más lógica de negocio
        privateBusinessLogic()
    }

    private fun privateBusinessLogic() {
        // Lógica de negocio
    }
}

// com.jesusdmedinac.app.viewmodels.MyViewModel
class MyViewModel : ViewModel() {
    @Inject
    lateinit var someManager: SomeManager

    private val _someState = MutableLiveData<State>()
    val someState: LiveData<State> = _someState

    fun doSomething() {
        // Lógica de negocio
    }
}

Primero que nada, ¿por qué estás usando someManager en el Activity/Fragment? No es responsable de gestionar la lógica de negocio. Es responsable de gestionar la UI. Pero podrías decir que como someManager es público, puede ser usado por otros componentes, ¿verdad? Incorrecto. Es público debido a la inyección de dependencias, pero no para ser usado por otros componentes. E incluso el ViewModel no debería estar usándolo. Dependiendo de la responsabilidad del manager, podríamos determinar en qué componente debería usarse.

Ahora, ¿por qué estás usando privateBusinessLogic en el Activity/Fragment? Perfectamente podríamos moverlo al ViewModel, ¿verdad? Pero ¿qué pasa si ese privateBusinessLogic depende de propiedades del Activity/Fragment? Podríamos hacer que privateBusinessLogic sea capaz de recibir esas propiedades como parámetros definiendo una data class o interfaz para abstraer los tipos de datos.

Encontré muchos ejemplos como este, donde los desarrolladores siempre intentan agregar la solución de código en el código existente, en lugar de refactorizar para seguir los principios SOLID.

Veamos una versión refactorizada de este código:

// com.jesusdmedinac.app.activities.MyActivity
class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)

        viewModel.someState.observe(this, Observer { state ->
            // Actualizar UI basado en el estado
        })

        // Lógica de negocio
        viewModel.doSomething()

        // Más lógica de negocio
        viewModel.doSomethingElse()

        // Más lógica de negocio
        viewModel.doNonPrivateBusinessLogic()
    }
}

// com.jesusdmedinac.app.viewmodels.MyViewModel
interface SomeManager {
    fun doSomething()
}

class MyViewModel @Inject constructor(
    private val someManager: SomeManager
) : ViewModel() {
    private val _someState = MutableLiveData<State>()
    val someState: LiveData<State> = _someState

    fun doSomething() {
        // Lógica de negocio
    }

    fun doSomethingElse() {
        // Lógica de negocio
        someManager.doSomething()
    }

    fun doNonPrivateBusinessLogic() {
        // Lógica de negocio
    }
}

El problema de Android - Context en todas partes

Otro problema muy común para los desarrolladores Android es usar Context en todas partes. Pero ¿cómo desarrollas una app Android sin usar Context? no lo haces. ¿Entonces? por eso necesitamos usar los principios SOLID.

¿Qué es un Context en Android? Es un objeto global que proporciona acceso a recursos y clases específicas de la aplicación, así como a clases de sistema específicas de la aplicación.