在android app开发过程中,经常会遇到代码写着写着就非常臃肿,有时候改动了一处,引发了关联性灾难,这不由得让我们思考如何才能把代码写的更具有扩展性和可维护性,架构这个东西并没有最好,只有最合适。

网上很多文章在写MVC、MVP、MVVM,组件化、插件化、单一职责、单例模式、工厂模式等,但是没有一篇是综合上述思想或者模式来谈架构设计。它们都属于架构的范畴,但是关注的维度不同。

一、针对MVC、MVP、MVVM,它关心的是数据从获取到展示的代码逻辑,所要解决的核心问题是数据和界面之间如何更好联系的问题,它的侧重点是界面和数据。然而这三种设计模式在每个开发人员心中都有各自的理解,现在这些模式与发明人当初发明时所想表达思想已经不尽相同,即使在当下,后台开发人员和前端开发人员的理解也不一样,甚至在android开发中,每个人都有不同的见解,但是我们的目的都是一样的,那就是解耦和复用。下面会以大部分人认同的理解来阐述这三种模式的异同。

MVC(model view controller) 在android中,model模型--默认就是业务数据层(包括业务数据的获取、以及数据本身),view 默认指xml文件 ,controller默认指activity,这种理解方式多少受到了android框架本身的影响,activity这个和界面强相关的实体,数据展示逻辑以及事件处理逻辑放在其中是顺理成章的事,由此如果这个activity业务非常复杂,那么它必然会变得很臃肿,但是反过来诸如以下代码:

public class GuidActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //简单的逻辑
        startActivity(Intent(this,MainActivity.javaClass))
    }
}

这是一个app启动界面GuidActivity,如果这个界面没有复杂的逻辑,这个时候采用默认的MVC又有何不可呢?配上注释,它的可读性非常强,针对这种场景,谁能说这样的代码不够优雅?所以做架构千万不要陷入误区,不要为了架构而架构。另外MVC在android中的上述解释其实非常牵强,因为activity严格来说也算做界面的一部分,所以这个view和controller的界限很模糊,基本上都融合在一起了,称它为M-VC一点都不为过,基于这个事实才诞生了MVP这种模式。

MVP(model view presenter)MVP针对上面这种activity和xml界限模糊的问题做出了解决方案,它单独抽出了Presenter层,其本质还是Controller,但是这种模式将activity彻底抛到了view的一边,也就是无论是xml、自定义空间还是activity,它都是属于view层,而presenter层单独创建了,它替代了原来在activity中的职责,负责向model层拿数据,也负责将数据塞给view层,从而将model和view隔离开来,所以android中大部分人认为的MVP在本质上才是真正的MVC模式,就相当于原来的MVC其实是假冒伪劣产品一样,以下代码就是一个完整的MVP结构。

class MVPActivity : AppCompatActivity() {
    var persenter: Presenter? = null;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        persenter = Presenter(this)
        persenter!!.requestData()
    }
    fun show(data: String) {
        findViewById<TextView>(R.id.text).text = data
    }
}
class Presenter constructor(view: MVPActivity) {
    var model: Model = Model()
    var view: MVPActivity
    init {
        this.view = view
    }
    fun requestData() {
        view.show(model.getDataFromWork())
    }
}
class Model {
    fun getDataFromWork(): String {
        return "data"
    }
}

当view和model不再联系时,MVP所带来的好处是view和model的可替换性,以及单元测试的便利性,还做到了presenter的重用,因为presenter单独抽离了,由于项目中在不同页面可能需要相同的业务,这个时候preseneter的代码就可以复用了,但是要做复用上面的代码还不够,必须要将view接口化,使得presenter和activity彻底解绑,presenter持有抽象的view就足够了。而对于model的接口化,它的目的是里氏替换,数据源可以从网络获取,同样可以从缓存获取,这个时候策略模式就可以上场了,而presenter的接口化更多的目的是业务的重用,比如一个业务中包含业务A和业B,其实并不需要重写一个Presenter,只要实现这两个Presenter接口,并让它们对应的实现去干活就可以了,如果该业务有新增就在其中加入独有的业务逻辑。MVP设计模式在android中的缺点也很明显,那就是内存泄漏风险,当presenter持有了activity的引用,一个presenter中的model做了耗时操作的同时activity关闭了,此时activity就泄漏了,所以要更好的使用MVP还要加上activity生命周期的维护,还有就是如果项目中的业务相对独立又没有任何重叠,MVP这种模式就非常鸡肋了,它会造成接口泛滥,一套MVP接口只用一次的尴尬在这种情景下显得非常突出,它唯一起到的作用就是控制层彻底分离了,针对这种情景就不需要面向接口了,因为你定义的接口就用了一次,失去了接口本来的意义,还不如直接了当的写代码,会让整体代码显得更加清爽,针对MVP的内存泄漏这一点,其实也是谷歌推崇MVVM的原因之一。

MVVM(model view viewmodel) 在MVVM中viewmodel有了感知生命周期的能力,它的本质还是控制层,只不过它没有持有view的引用,在这种模式多了一个数据绑定概念,谷歌为我们开发者提供好了绑定工具api--databinding,databinding(数据绑定)使得数据实体和xml绑定在一起,更新数据实体中的数据,xml界面就更新,它是视图绑定(viewbinding)和可观察数据对象(observable)两者合一的应用,而livedata是obervable的一种具备生命周期感知的应用,本质上它们是实现android mvvm的一整套工具,所以android中MVVM注重对谷歌工具的使用,感知这个概念贯穿整个模式中,这个感知包括activity、fragment的生命周期感知,也包含数据变化的感知,在具体的MVVM实现中,viewmodel通过model获取数据,获取到的数据包装成可感知的数据,view层对数据变化做监听,无论使用databinding也好,还是使用livedata也好,都是监听数据变化来更新UI,它和MVP的区别在于P将数据主动喂给V,而MVVM中的view是去监听数据的变化来触发更新。

在MVVM中,正因为谷歌为我们提供了工具,所以代码量才没有MVP这种接口实现来的多,同时规避了MVP自动管理生命周期的尴尬,但是诸如使用databinding这种工具增加了代码的调试难度,同时编码过程中频繁在xml和java中切换,让xml文件含有数据逻辑处理本身就是一种耦合的表现,它并不利于xml文件的复用,databinding的使用场景应该是数据展示层变化较小同时数据展示纯粹(一个xml中绑定的实体数不多,但是实体中的数据数目较多时)的业务环境,大部分业务变化较快、数据结构多变的时候就不适合使用databinding,viewmodel+livedata+viewbinding是一个不错的选择。

二、组件化、插件化在架构层面的侧重点是业务功能拆分,其中组件化是将应用按功能拆分不同组件或者模块,组件之间不再相互依赖,组件可以依赖同一套libray,组件化最大的好处是有利于团队开发,在团队开发中不需要等待别人的代码,自己可以进行独立测试,写库的写库,写模块的写模块,互不干涉,在android中一个组件对应着一个module,组件化还有一个好处就是可以提高编译效率。在大型项目中使用组件化是必要的,然而在一些独立开发的小型项目中使用组件化反而得不偿失,因为组件化多少会带来代码和配置增多。插件化核心在于动态加载模块,在庞大的工程中,可以按需下载功能包,它不是官方支持的,是一种取巧的技术,在国内盛行,因为android的开源,framework层代码对程序员透明,插件化是android开发者解决一个又一个的问题后诞生的,首先解决的是加载dex文件,android PathClassLoader就能解决,其次是资源文件的加载(res、assets、so) AssetsManager能解决,在资源加载的过程中还要考虑资源id重复或者找不到的问题,再次要解决activity清单文件注册问题,在分析framework后可以采用hook,也就是在调用ams时和ams返回后两个地方进行移花接木,插件化解决了初安装包体太大的问题,按需加载也解决了功能动态下发的问题,但是维护性很高,比如每个android新版本都要做兼容,甚至完全要看谷歌让不让用。

组件化和插件化都是业务架构范畴,如何拆分更好的拆分业务才是核心。

三、单一职责、开闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特原则是程序设计的六大原则,它是代码层面的编程思想,其中各种代码示例在我的另一篇文章有完整的体现。

设计模式六大原则

这些原则是一种编程指导思想,目的是让程序员写出更容易扩展和维护的代码,如果程序设计比作做人,那么六大设计原则就是做人的道德规范,而23种设计模式就好比行为准则一样。

四、23种设计模式

  1. 创建型设计模式
    与对象创建有关;共5种:单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式;
    单例模式
    6种单例模式.
  2. 结构型设计模式
    共7种:适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型设计模式
    共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。、

不管是设计原则还是设计模式,它们在架构设计中无处不在,每一种设计模式都有它适用的场景,比如项目中的管理类,管理大总管一般适合采用单例模式,一旦内存中出现了两个管理者,很可能产生混乱,建造者模式它的目的按需实例化一个对象,工厂模式的侧重点是屏蔽实例化对象的复杂性,原型模式侧重点是解决创建对象的成本问题。装饰者模式是为了增强类的能力而存在,代理模式其实是迪米特原则的体现,就是对象之间不能胡乱调用等等。

架构切不可为了设计模式而设计模式,设计模式一定有它适用的场景,在实际开发中一定要随机应变,这才是真正的架构设计。

发表回复

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