【Android爬坑日记四】组合替代继承,减少Base类滥用

布景

先说一下布景,当接触了比较多的项目之后,其实会发现每一个项目都会封装BaseActivity、BaseFragment等等。其实初衷其实是好的。每一个Activity和Fragment都是许多模板代码的,为了削减模板代码,封装进Base类其实是一种比较便利且可行的挑选。

Base类涵盖了笼统、承继等面向对象特性,用得好会削减许多样板代码,可是一旦乱用,会对项目有许多坏处。

举个比如

当项目大了,需求封装进Base类的逻辑会非常多,比如说打印生命周期、ViewBinding 或许DataBinding封装、埋点、监听播送、监听EventBus、展现加载界面、弹Dialog等等其他事务逻辑,更有甚者把需求Context的函数都封装进Base类中。

以下举一个BaseActivity的比如,里面封装了上面所说的大部分状况,实际状况或许更多。

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()
    }
}

其实看起来还好,可是在运用的时分难免会遇到一些问题,关于半途接手项意图人来说问题更加明显。咱们从半途接手项意图心路历程看看Base类的缺陷。

心路历程

  1. 当创立新的Activity或许Fragment的时分需求想想有没有逻辑能够复用,就去找Base类,或许写Base类的人不同,发现一个项目中或许会存在多个Base类,乃至Base类依然有多个Base子类完成不同逻辑。这个时分就需求去查看分析每个Base类别离完成了什么功用,决议承继哪个。

  2. 假如一个项目中只要一个Base类的话,仍需求看看Base类完成了什么逻辑,没有完成什么逻辑,避免重复写样板代码。

  3. 当呈现Base类完成了的,而自己本身并不想需求,例如不想监听播送或许不想用ViewModel,关于不想监听播送的状况就要特别做适配,例如往Base类加标志位。关于不想用ViewModel可是由于泛型限制,仍是只能传进去,不然没法承继。

  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类应该运用组合的办法来运用,这关于项意图可保护性和代码的可调试性是有好处的。

参考

/post/707789…