[Android Climbing Diary 4] Combinando herencia alternativa para reducir el abuso de la clase Base

antecedentes

Hablemos primero de los antecedentes. Cuando entre en contacto con más proyectos, encontrará que cada proyecto encapsulará BaseActivity, BaseFragment, etc. De hecho, la intención original es realmente buena. Cada Actividad y Fragmento tiene mucho código de plantilla. Para reducir el código de plantilla, en realidad es una opción más conveniente y factible encapsularlo en la clase Base.

La clase Base cubre funciones orientadas a objetos como la abstracción y la herencia. Si se usa bien, reducirá una gran cantidad de código repetitivo, pero si se abusa de él, tendrá muchos inconvenientes para el proyecto.

por ejemplo

Cuando el proyecto es grande, habrá una gran cantidad de lógica que debe encapsularse en la clase Base, como el ciclo de vida de impresión, la encapsulación de ViewBinding o DataBinding, la incrustación, el monitoreo de la transmisión, el monitoreo de EventBus, la visualización de la interfaz de carga, la aparición de Diálogo y otros lógica de negocios y más Incluso las funciones que requieren Contexto están encapsuladas en la clase Base.

El siguiente es un ejemplo de BaseActivity, que encapsula la mayoría de las situaciones mencionadas anteriormente, y la situación real puede ser más.

abstract class BaseActivity<T: ViewBinding, VM: ViewModel>: AppCompatActivity {

    protected lateinit var viewBinding: T
    
    protected lateinit var viewModel: VM

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 打印日志!!
        ELog.debugLifeCycle("${this.localClassName} - onCreate")
       
        // 初始化viewModel
        viewModel = initViewModel()
        // 初始化视图!!
        initView()
        // 初始化数据!!
        initData()
        // 注册广播监听!!
        registerReceiver()
        // 注册EventBus事件监听!!
        registerEventBus()
        
        // 省略一堆业务逻辑!
        
        // 设置导航栏颜色!!
        window.navigationBarColor = ContextCompat.getColor(this, R.color.primary_color)
    }
    
    protected fun initViewModel(): VM {
        // 初始化viewModel
    }
    
    private fun initViewbinding() {
        // 初始化viewBinding
    }
    
    // 让子类必须实现
    abstract fun initView()
    
    abstract fun initData()
    
    private fun registerReceiver() {
        // 注册广播监听
    }
    
    private fun unregisterReceiver() {
        // 注销广播监听
    }
    
    private fun registerEventBus() {
        // 注册EventBus事件监听
    }
    
    protected fun showDialog() {
        // 需要用到Context,因此也封装进来了
    }
    
    override fun onResume() {
        super.onResume()
        ELog.debugLifeCycle("${this.localClassName} - onResume")
    }

    override fun onPause() {
        super.onPause()
        ELog.debugLifeCycle("${this.localClassName} - onPause")
    }
    
    override fun onDestroy() {
        super.onDestroy()
        ELog.debugLifeCycle("${this.localClassName} - onDestroy")
        unregisterReceiver()
    }
}
复制代码

De hecho, se ve bien, pero inevitablemente encontrará algunos problemas al usarlo, y los problemas son más obvios para aquellos que se hacen cargo del proyecto en el medio. Echemos un vistazo a los defectos de la clase Base del proceso de hacerse cargo del proyecto en el medio.

viaje mental

  1. Al crear una nueva Actividad o Fragmento, debe pensar si hay alguna lógica que se pueda reutilizar y luego ir a la clase Base. Tal vez las personas que escriben la clase Base son diferentes y descubren que puede haber varias clases Base. en un proyecto, e incluso la clase Base aún tiene varias subclases Base que implementan una lógica diferente. En este momento, debe verificar y analizar qué funciones implementa cada clase Base y decidir cuál heredar.

  2. Si solo hay una clase Base en un proyecto, aún necesita ver qué lógica implementa la clase Base y qué lógica no implementa para evitar la escritura repetitiva de código repetitivo.

  3. Cuando se implementa la clase Base, pero no quiere necesitarla, por ejemplo, no quiere escuchar la transmisión o no quiere usar ViewModel, necesita hacer adaptaciones especiales si no lo hace. desea escuchar la transmisión, como agregar una bandera a la clase Base. Para aquellos que no quieren usar ViewModel pero debido a restricciones genéricas, solo se pueden pasar, de lo contrario no se pueden heredar.

  4. 当发现自己集成Base类出BUG了,就要考虑改子类还是改Base类,由于大量的类都集成了Base类,显然改Base类比较麻烦,于是改自己比较方便。

  5. 如果一个Activity中展示了多个Fragment,可能会有业务逻辑的重复,其实只需要一个就好了。

其实第一第二点还好,时间成本其实没有重复写样板代码那么高。但是第三点的话其实用标志位来决定Base类的功能哪个需要实现哪个不需要实现并不是一种优雅的方式,反而需要重写的东西多了几个。第四点归根到底就是Base类其实并不好维护。

爬坑

那么对于Base类怎样实践才比较优雅呢?在我看来组合替代继承其实是一种不错的思路。对于Kotlin first的Android项目来说,组合替代继承其实是比较容易的。以下仅代表个人想法,有不同意见可以交流一下。

成员变量委托

对于ViewModel、Handler、ViewBinding这些Base变量使用委托的方式是比较方便的。

对于ViewBinding委托可以看看我之前的文章,使用起来其实是非常简单的,只需要一行代码即可。

// Activity
private val binding by viewBinding(ActivityMainBinding::inflate)
// Fragment
private val binding by viewBinding(FragmentMainBinding::bind)
复制代码

对于ViewModel委托,官方库则提供了一个viewBindings委托函数。

private val viewModel:HomeViewModel by viewModels()
复制代码

需要在Gradle中引入ktx库

implementation 'androidx.fragment:fragment-ktx:1.5.1'
implementation 'androidx.activity:activity-ktx:1.5.1'
复制代码

而对于Base变量则尽量少封装在Base类中,需要使用可以使用委托,因为如果实例了没有使用其实是比较浪费内存资源的,尽量按需实例。

扩展方法

对于需要用到Context上下文的逻辑封装到Base类中其实是没有必要的,在Kotlin还没有流行的时候,如果说需要使用到Context的工具方法,使用起来其实是不太优雅的。

例如展示一个Dialog:

class DialogUtils {
    public static void showDialog(Activity activity, String title, String content) {
        //  逻辑
    }
}
复制代码

使用起来就是这样:

class MyActivity : AppCompatActivity() {
    ...
    fun initButton() {
        button.setOnClickListener {
            DialogUtils.showDialog(this, "title", "content")
        }
    }
}
复制代码

使用起来可能就会有一些迷惑,第一个参数把自己传进去了,这对于展示Dialog的语义上是有些奇怪的。按理来说只需要传title和content就好了。

这个时候就会有人想着把这个封装到Base类中。

public abstract class BaseActivity extends AppCompatActivity {

    protected void showDialog(String title, String content) {
        // 这里就可以用Context了
    }
}

复制代码

使用起来就是这样:

class MyActivity : AppCompatActivity() {
    ...
    fun initButton() {
        button.setOnClickListener {
            showDialog("title", "content")
        }
    }
}

复制代码

是不是感觉好很多了。但是写在Base类中在Java中比较好用,对于Kotlin则完全可以使用扩展函数语法糖来替代了,在使用的时候和定义在Base类是一样的。

fun Activity.showDialog(title: String, content: String) {
    // this就能获取到Activity实例
}

class MyActivity : AppCompatActivity() {
    ...
    fun initButton() {
        button.setOnClickListener {
            // 使用起来和定义在Base类其实是一样的
            showDialog("title", "content")
        }
    }
}

复制代码

这也说明了,需要使用到Context上下文的函数其实不用在Base类中定义,直接定义在顶层就好了,可以减少Base类的逻辑。

注册监听器

对于注册监听器这种情况则需要分情况,监听器是需要根据生命周期来注册和取消注册的,防止内存泄漏。对于不是每个子类都需要的情况,有的人可能觉得提供一个标志位就好了,如果不需要的话让子类重写。如果定义成抽象方法则每个子类都要重写,如果不是抽象方法的话,子类可能就会忘记重写。在我看来获取生命周期其实是比较简单的事情。按需添加代码监听就好了。

那么什么情况需要封装在Base类中呢?

  • 怕之后接手项目的人忘记写这部分代码,则可以写到Base类中,例如打印日志或者埋点。

  • 而对于界面太多难以测试的功能,例如收到被服务器踢下线的消息跳到登录页面,这个可以写进Base类中,因为基本上每个类都需要监听这种消息。

总结

没有最优秀的架构,只有最适合的架构!对于Base类大家的看法都不一样,追求更少的工作量完成更多事情这个目的是统一的。而Base类一旦臃肿起来了会造成整个项目难以维护,因此对于Base类应该辩证看待,养成只有必要的逻辑才写在Base类中的习惯,feature类应该使用组合的方式来使用,这对于项目的可维护性和代码的可调试性是有好处的。

参考

juejin.cn/post/707789…

Supongo que te gusta

Origin juejin.im/post/7144671989159067656
Recomendado
Clasificación