关于android:Android极简MVVM从一个基类库谈起

Hello啊各位老铁,明天带来一个陈词滥调的技术,MVVM,这篇文章,次要具体介绍如何封装一个MVVM的基类库,以及MVVM架构模式在理论业务中的用法,最初会把理论的封装代码开源,并提供近程依赖,不便给到大家应用以及二次批改,尽量做到细致入微,浅显易懂,OK,废话不多赘述,咱们进入注释。

这篇文章大略会依照以下几个模块进行论述,此次封装,做到绝无第三方依赖,都是Android原生的代码封装,请放心使用,如果您想间接进行应用,请间接跳到第4步,集成应用即可,此次的封装,和目前支流的MVVM架构模式,会完满符合,让架构模式简单化,让业务代码清晰化,必须值得举荐应用。

一、MVVM简略概括

二、基于MVVM模式如何封装基类库

三、实战封装

四、封装后在业务中如何应用

五、开源以及Demo查看

舒适提醒:内容稍多,请合理安排好工夫,如果不想查阅具体封装过程,底部有开源地址,能够间接查看。

一、MVVM简略概括

MVVM的开发模式,相对来说低耦合,业务之间逻辑显得也非常明显,Model层负责将申请的数据交给ViewModel层;ViewModel层负责将申请到的数据做业务逻辑解决,最初交给View层去展现,与View一一对应;View层只负责界面绘制刷新,不解决业务逻辑,非常适合进行独立模块开发。

三层简略概括

1、Model:数据层,蕴含数据实体和对数据实体的操作。

2、View:视图层,对应于Activity,XML,View,负责数据显示以及用户交互。

3、ViewModel:关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方。

须要留神:

1、View只做和UI相干的工作,不波及任何业务逻辑,不波及操作数据,不解决数据,也就是UI和数据是严格离开的。

2、ViewModel只做和业务逻辑相干的工作,不波及任何和UI相干的操作,不持有控件援用,不更新UI。

二、基于MVVM模式如何封装基类库

MVVM咱们曾经清晰,然而针对现有的三层,咱们如何进行拆解封装呢?面对这样的一个问题,咱们也是须要从三层以及和理论的业务进行相结合,从理论业务中来,也要从理论业务中去,这是咱们封装的一个潜在因素,一旦脱离了理论,封装的再优良,也只是一个花瓶,中看不中用。

针对MVVM中的三层,其实,咱们在封装中,也是基于这三层,View,ViewModel和Model。View中,在理论的开发中,个别针对Activity和Fragment进行零碎的抽取封装,ViewModel个别会抽取一个父类,做一些公共的办法或属性配置,Model层个别封装的较少,依据理论业务,须要具体问题具体分析。

Activity和Fragment的封装思路,其实是统一的,须要以简略和简单两种方向进行抽取,一种是简略的页面继承应用,一种是简单的页面继承应用,这样辨别的一个目标,就是,专职专用,防止大材小用,而具体的封装,除了使得代码简洁化,更重要的拓展化,不便子类的调用。

在具体封装的时候,与理论业务相结合,这个无比重要,比方理论的大部分页面,都带有一个标题栏,那么标题栏就能够间接封装父类外面,像子类拓展出,更改题目,右侧按钮,左侧按钮等功能属性;除了对立的标题栏,另外就是子类的视图了,对于子类的视图传递,这个是必须的,能够间接形象出一个必须要实现的办法,其余的,比方状态栏的扭转,缺省页的设置等等,也须要在父类中对立的给出。

简单的页面是基于简略的页面而来的,这里的简单,个别是蕴含很多逻辑的解决,那么,咱们就能够减少ViewModel层和Model层了,目前基于DataBinding的实现形式,无论简略和简单,都是必须须要思考的,也就是说在父类中,咱们就须要向子类提供出能够拿到的databinding和viewmodel,个别以泛型的形式引入,这样子类再继承的时候,就能够很不便的进行调用。

在简单的页面,也就是蕴含ViewModel层和Model层的时候,须要思考绑定视图variable的传递,也就是以后的ViewModel和那个xml进行绑定,当然这是在须要的时候,必须要操作的,除了视图绑定,常见的,数据申请状态,比方申请胜利,申请失败,缺省页显示和暗藏,Dialog的显示和暗藏,LiveData的数据回传等等,在简单的页面中也是须要咱们思考的,除此之外,ViewModel中如何和View层的生命周期绑定,在理论的业务中也是不得不须要思考的。

除了以上的惯例思考,在理论的业务中,比方事件消息传递,PagerAdapter应用,状态栏通明等很多和基类的相干的性能,咱们其实也能够进行封装进去,便于子类的调用。

三、实战封装

通过第2条中的拆解和具体的封装思路,无妨咱们进行实战一下,因为Activity和Fragment的封装思路以及相干属性和办法,大部分都是雷同的,所以目前只介绍Activity,更具体的封装,还请大家参考源码。

1、Activity的简略封装

简略封装,不携带ViewModel,只传递ViewDataBinding,子类必须重写的办法只有一个initData,其余均为选择性重写,如果绝对逻辑比较简单的页面,能够继承此类。

一个很简略的一般封装,就是把共有的常见的,封装到父类里,便于子类的调用,具体什么办法,什么逻辑进行采取封装,须要咱们依据具体业务或者公司的相干状况而定,以下是源码。

abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes layoutId: Int = 0) :
    AppCompatActivity(layoutId) {
 
 
    private var mActionBarView: ActionBarView? = null
    private var mLayoutError: LinearLayout? = null
    private var mLayoutId = layoutId
    lateinit var mBinding: VB
 
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        try {
            //默认状态栏为白底黑字
            darkMode(BaseConfig.statusBarDarkMode)
            statusBarColor(ContextCompat.getColor(this, BaseConfig.statusBarColor))
            setContentView(R.layout.activity_base)
            val baseChild = findViewById<LinearLayout>(R.id.layout_base_child)
            mLayoutError = findViewById(R.id.layout_empty_or_error)
            mActionBarView = findViewById(R.id.action_bar)
            if (mLayoutId == 0) {
                mLayoutId = getLayoutId()
            }
 
 
            if (savedInstanceState != null && getIntercept()) {
                noEmptyBundle()
                return
            }
 
 
            val childView = layoutInflater.inflate(mLayoutId, null)
            baseChild.addView(childView)
            mBinding = DataBindingUtil.bind(childView)!!
 
 
            initView()
            initData()
        } catch (e: Exception) {
            e.printStackTrace()
            noEmptyBundle()
        }
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取视图id
     */
    open fun getLayoutId(): Int {
        return 0
    }
 
 
    open fun initView() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:初始化数据
     */
    abstract fun initData()
 
 
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:动静扭转状态栏色彩和题目
     */
    fun setDarkTitle(dark: Boolean, color: Int, title: String) {
        try {
            darkMode(dark)
            statusBarColor(ContextCompat.getColor(this, color))
            setBarTitle(title)
 
 
        } catch (e: Exception) {
            e.printStackTrace()
        }
 
 
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:设置题目
     */
    fun setBarTitle(title: String) {
        mActionBarView!!.visibility = View.VISIBLE
        mActionBarView!!.setBarTitle(title)
    }
 
 
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:暗藏左侧按钮
     */
 
 
    fun hintLeftMenu() {
        mActionBarView!!.hintLeftBack()
    }
 
 
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取ActionBarView
     */
    fun getActionBarView(): ActionBarView {
        return mActionBarView!!
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:暗藏标题栏
     */
    fun hintActionBar() {
        mActionBarView?.visibility = View.GONE
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:Bundle为空进行拦挡,解决扭转权限后重回App解体问题
     */
    open fun getIntercept(): Boolean {
        return false
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:Bundle为空时的逻辑解决,解决扭转权限后重回App解体问题
     */
    open fun noEmptyBundle() {}
 
 
 
 
    override fun onDestroy() {
        super.onDestroy()
        try {
            LiveDataBus.removeObserve(this)
            LiveDataBus.removeStickyObserver(this)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
 
 
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:通明状态栏
     */
    fun translucentWindow(dark: Boolean) {
        try {
            immersive(0, dark)
        } catch (e: Exception) {
            e.printStackTrace()
        }
 
 
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:设置缺省页
     */
    fun setEmptyOrError(view: View) {
        mLayoutError?.visibility = View.VISIBLE
        mLayoutError?.removeAllViews()
        mLayoutError?.addView(view)
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:暗藏
     */
    fun hintEmptyOrErrorView() {
        mLayoutError?.visibility = View.GONE
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取谬误或为空的view
     */
    fun getEmptyOrErrorView(): LinearLayout {
        return mLayoutError!!
    }
}

波及的办法概述

办法名 参数 概述
getLayoutId 无参 子类传递的layout,用于加载视图,能够通过构造方法传递,也能够通过此办法传递。
initView 无参 初始化View,非必须重写
initData 无参 初始化数据
setDarkTitle dark: Boolean, color: Int, title: String,1、dark: Boolean,状态栏色彩,true就是彩色,false就是红色。2、color: Int,状态栏背景色彩,3、title: String,标题栏内容 设置题目,状态栏背景及色彩
setBarTitle title: String,标题栏内容 设置题目
hintLeftMenu 无参 暗藏左侧按钮
getActionBarView 无参 获取标题栏View,能够操作标题栏里的任何控件
hintActionBar 无参 暗藏标题栏
translucentWindow dark: Boolean,状态栏色彩,true就是彩色,false就是红色 通明状态栏
setEmptyOrError view: View,传递的缺省View视图 设置缺省视图
hintEmptyOrErrorView 无参 暗藏缺省视图
getEmptyOrErrorView 无参 获取缺省视图

简略的Activity没有什么好说的,都是中规中矩,具体的应用请大家看第四条,具体应用即可。

2、Activity的简单封装

也谈不上简单,只是在继承简略页面的根底之上多加了一个ViewModel,绝对于比较复杂的页面,就能够继承此类,此类,拓展了ViewModel,能够在ViewModel里进行逻辑的书写,此类也是MVVM的规范执行,V继承于BaseVMActivity,VM继承于BaseViewModel,至于M,能够在VM中通过getRepository办法进行获取。

具体代码逻辑如下:

BaseVMActivity继承于BaseActivity。

abstract class BaseVMActivity<VB : ViewDataBinding, BM : BaseViewModel>(@LayoutRes layoutId: Int = 0) :
    BaseActivity<VB>(layoutId) {
 
 
    lateinit var mViewModel: BM
 
 
    override fun initData() {
        mViewModel = getViewModel()!!
        val variableId = getVariableId()
        if (variableId != -1) {
            mBinding.setVariable(getVariableId(), mViewModel)
            mBinding.executePendingBindings()
        }
        initVMData()
        observeLiveData()
        initState()
        lifecycle.addObserver(mViewModel)
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取绑定的xml id
     */
    open fun getVariableId(): Int {
        return -1
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:初始化状态
     */
    private fun initState() {
        mViewModel.mStateViewLiveData.observe(this, {
            when (it) {
                StateLayoutEnum.DIALOG_LOADING -> {
                    dialogLoading()
                }
                StateLayoutEnum.DIALOGD_DISMISS -> {
                    dialogDismiss()
                }
                StateLayoutEnum.DATA_ERROR -> {
                    dataError()
                }
                StateLayoutEnum.DATA_NULL -> {
                    dataEmpty()
                }
                StateLayoutEnum.NET_ERROR -> {
                    netError()
                }
                StateLayoutEnum.HIDE -> {
                    hide()
                }
            }
        })
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:初始化数据
     */
    abstract fun initVMData()
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:LiveData的Observer
     */
    open fun observeLiveData() {
 
 
    }
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:dialog加载
     */
    open fun dialogLoading() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:dialog暗藏
     */
    open fun dialogDismiss() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:数据谬误
     */
    open fun dataError() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:数据为空
     */
    open fun dataEmpty() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:网络谬误或申请谬误
     */
    open fun netError() {}
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:暗藏某些布局或者缺省页等
     */
    open fun hide() {}
 
 
    private fun getViewModel(): BM? {
        //这里取得到的是类的泛型的类型
        val type = javaClass.genericSuperclass
        if (type != null && type is ParameterizedType) {
            val actualTypeArguments = type.actualTypeArguments
            val tClass = actualTypeArguments[1]
            return ViewModelProvider(
                this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(application)
            )
                .get(tClass as Class<BM>)
        }
        return null
    }
 
override fun onDestroy() {
    super.onDestroy()
    try {
        lifecycle.removeObserver(mViewModel)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}
 
}

封装波及的办法概述

办法名 参数 概述
getVariableId 无参 获取绑定的xml variable,也就是以后的xml和哪个对象进行绑定,用于xml里间接数据绑定
initVMData 无参 初始化数据,必须要实现的办法
observeLiveData 无参 LiveData的Observer,UI层监听ViewModel层的数据扭转
dialogLoading 无参 dialog加载
dialogDismiss 无参 dialog暗藏
dataError 无参 数据谬误
dataEmpty 无参 数据为空
netError 无参 数据谬误
hide 无参 暗藏缺省页等其余页面

BaseViewModel

BaseViewModel绝对比较简单,只提供了一个能够获取Repository的办法,还有一个是刷新UI视图的一个LiveData,就是数据申请,Dialog加载,缺省页加载的状态。更改状态,只须要调用changeStateView办法即可,子类能够重写生命周期办法,便于生命周期的思考。

 
open class BaseViewModel : ViewModel() , BaseObserver{
    /**
     * 管制状态视图的LiveData
     */
    val mStateViewLiveData = MutableLiveData<StateLayoutEnum>()
 
 
    /**
     * 更改状态视图的状态
     */
    public fun changeStateView(
        state: StateLayoutEnum
    ) {
        // 对参数进行校验
        when (state) {
            StateLayoutEnum.DIALOG_LOADING -> {
                mStateViewLiveData.postValue(StateLayoutEnum.DIALOG_LOADING)
            }
            StateLayoutEnum.DIALOGD_DISMISS -> {
                mStateViewLiveData.postValue(StateLayoutEnum.DIALOGD_DISMISS)
            }
            StateLayoutEnum.DATA_ERROR -> {
                mStateViewLiveData.postValue(StateLayoutEnum.DATA_ERROR)
            }
            StateLayoutEnum.DATA_NULL -> {
                mStateViewLiveData.postValue(StateLayoutEnum.DATA_NULL)
            }
            StateLayoutEnum.NET_ERROR -> {
                mStateViewLiveData.postValue(StateLayoutEnum.NET_ERROR)
            }
            StateLayoutEnum.HIDE -> {
                mStateViewLiveData.postValue(StateLayoutEnum.HIDE)
            }
        }
 
 
    }
   
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取Repository
     */
    inline fun <reified R> getRepository(): R? {
        try {
            val clazz = R::class.java
            return clazz.newInstance()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期初始化
 */
override fun onCreate() {
}
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期页面可见
 */
override fun onStart() {
}
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期页面获取焦点
 */
override fun onResume() {
}
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期页面失去焦点
 */
override fun onPause() {
 
}
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期页面不可见
 */
override fun onStop() {
    
}
 
/**
 * AUTHOR:AbnerMing
 * INTRODUCE:生命周期页面销毁
 */
override fun onDestroy() {
}
 
}

简单的Activity,大家能够发现,其实就是规范的MVVM模式封装,Fragment的封装也是基于此,搞清楚上述,基本上咱们这个基类库就实现了大半,的确也没什么好说的,大家间接看应用吧。

四、封装后在业务中如何应用

通过以上的封装,咱们在业务层所有的页面就能够继承父类进行应用,以达到代码的高度对立,使得架构模式简单化,让业务代码清晰化,目前的封装,大家能够间接封装成库或者打成aar给到其余开发者应用,目前我曾经上传到近程,不想麻烦的老铁,能够间接依照上面的步骤进行应用。

1、在你的根我的项目下的build.gradle文件下,引入maven。

 
allprojects {
    repositories {
      maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }
    }
}

2、在你须要应用的Module中build.gradle文件下,引入依赖。

 
dependencies {
  implementation 'com.vip:base:1.0.2'
}

通过以上的Maven仓库依赖,咱们就能够欢快的进行应用了,上面针对各个封装的性能进行一个简略的演示,当然,大家能够间接看源码中的实例,那里绝对比拟全面。

1、一般的Activity的继承

如果,你的Activity页面逻辑比较简单,倡议继承BaseActivity,此父类,没有与ViewModel相结合,只蕴含失常且简略的逻辑解决,目前必须重写的只有一个initData办法,其余办法,大家能够依据业务重写即可。

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:初始化数据
    */
    override fun initData() {
        setBarTitle("主页")
    }
}

2、ViewModel模式Activity的继承

View层,须要继承BaseVMActivity

class TestViewModelActivity : BaseVMActivity<ActivityViewModelBinding,
        TestViewModel>(R.layout.activity_view_model) {
 
 
    override fun initVMData() {
        setBarTitle("ViewModel形式应用")
    }
 
}

ViewModel层,须要继承BaseViewModel

理论的业务中,遇到网络申请,缺省页展现,Dialog显示暗藏,调用changeStateView办法,UI层只须要重写对应的办法即可。

 
class TestViewModel : BaseViewModel() {
 
 
      /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取须要的Repository
     */
    private val repository by lazy {
        getRepository<TestRepository>()
    }
 
}

Model层,个别依据理论须要,进行具体的封装应用。

 
class TestRepository {
 
}

3、DataBinding模式应用

View层,继承BaseVMActivity,返回以后视图的绑定variable

 
class DataBindActivity :
    BaseVMActivity<ActivityDataBindBinding,
            DataBindViewModel>(R.layout.activity_data_bind) {
 
 
    override fun initVMData() {
        setBarTitle("DataBinding应用")
    }
 
 
    override fun getVariableId(): Int {
        return BR.data
    }
}

ViewModel层继承BaseViewModel

 
class DataBindViewModel : BaseViewModel() {
 
 
    var oneWayContent = "单向绑定数据测试"
 
 
    var twoWayContent = "双向绑定数据测试"
 
 
    /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取双向绑定数据
     */
    var clickListener = View.OnClickListener {
 
 
        Toast.makeText(it.context, twoWayContent, Toast.LENGTH_SHORT).show()
    }
}

XML视图,间接绑定

 
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
 
 
    <data>
 
 
        <variable
            name="data"
            type="com.abner.base.bind.DataBindViewModel" />
    </data>
 
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingLeft="@dimen/gwm_dp_20"
        android:paddingRight="@dimen/gwm_dp_20">
 
 
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="@dimen/gwm_dp_20"
            android:text="@{data.oneWayContent}" />
 
 
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/gwm_dp_20"
            android:hint="双向绑定"
            android:text="@={data.twoWayContent}" />
 
 
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="@dimen/gwm_dp_20"
            android:onClick="@{data.clickListener}"
            android:text="获取双向绑定数据" />
 
 
    </LinearLayout>
</layout>

4、Fragment的简略应用

如果,你的Fragment页面逻辑比较简单,倡议继承BaseFragment,此父类,没有与ViewModel相结合,只蕴含失常且简略的逻辑解决,目前必须重写的只有一个initData办法,其余办法,大家能够依据业务重写即可。

class TestPagerFragment : BaseFragment
    <FragmentTestPagerBinding>(R.layout.fragment_test_pager) {
 
 
    override fun initData() {
 
 
    }
}

5、ViewModel模式Fragment的继承

View层,须要继承BaseVMFragment

class TestViewModelPagerFragment :
    BaseVMFragment<FragmentTestPagerBinding,
            TestFragmentViewModel>(R.layout.fragment_test_pager) {
 
 
 
 
    override fun initVMData() {
 
    }
 
 
}

ViewModel层,须要继承BaseViewModel

class TestFragmentViewModel :BaseViewModel(){
 
 
        /**
     * AUTHOR:AbnerMing
     * INTRODUCE:获取须要的Repository
     */
    private val repository by lazy {
        getRepository<TestRepository>()
    }
 
}

Model层,个别依据理论须要,进行具体的封装应用。

 
class TestRepository {
 
}

6、Fragment的DataBinding模式应用和Activity相似,就不赘述了。

7、事件音讯总线应用

一般事件发送

    LiveDataBus.send("send", "我发送了一条一般音讯")

一般发送事件接管

 
LiveDataBus.observe(this, "send", Observer<String> {
            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        })

粘性事件发送

 
LiveDataBus.sendSticky("sendSticky", "我发送了一条粘性事件音讯")

粘性事件接管

 
  LiveDataBus.observeSticky(this, "sendSticky", Observer<String> {
            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
        })

更多的其余性能应用,大家间接看Github即可,上边有比拟清晰的介绍。

五、开源以及Demo查看

以上的封装,目前曾经开源,大家能够下载查看源码,或者进行二次更改应用,地址是:

https://github.com/AbnerMing888/VipBase

相干Demo,大家能够down下我的项目,运行即可,这里简略贴张效果图:

目前的封装,没有过多的冗余代码,齐全能够满足理论的业务须要,大家能够依照这种模式试验一番,遇到问题,能够多多交换,毕竟,技术是凋谢的,交换中能力一直的提高,当然了,须要联合本人的理论业务进行应用,毕竟我的项目中不应存在多个架构模式,MVC也好,MVP,MVVM,MVI也罢,无论应用哪种,适宜的才是最好的。

好了,各位老铁,这篇文章就到这里,下篇文章《组件化开发,从未如此简略》正在撰写中,大家敬请期待!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理