1、背景
在软件开发中,如何做到低成本并能快速高质量交付,一直是各个软件公司追求的目标。下面从纯技术角度谈谈这个话题。
我们知道在面向对象设计中,是有一些设计原则要遵循的。例如要软件设计目标:正确性,健壮性,高扩展,高复用,高效性。
假如我们交付5个产品中,每个产品代码复用度达到90%和0%复用,我们分别计算成本,假设每个项目100W成本(计算公式先用最简单,实际会有些差异)
0%复用的,总成本=100+100+100+100+100=500W
90%复用的,总成本=100+10+10+10+10=140W
从上述数据可以看到,复用度是影响成本一个非常重要的因数。复用度越高,复用的项目数量越多,节约成本就越多,所以上述设计目标最终折射到商业行为中就是如何最大程度节约开发成本。
在上述指导思想下,我们公司开发产品,都是组件化开发,通过应用工厂把各个业务组装起来。以组件(业务划分的一个单元)为颗粒进行复用。组装过程中,其实就是对业务属性和业务之间调用进行配置化。这样带来一个新的问题:不同产品页面呈现差异性不大,如何满足同一个业务不同呈现需求,同时又要复用同一个业务组件,就是下面讲述要解决的问题。
2、解决方案
2.1 方案设计
最直接的想法是,在业务组件中为不同的产品开发不同的界面。但是当这个业务组件被200产品复用,要开发200个页面?这个业务组件项目组人力是有限的情况下,同时来20个优先级很高的需求,这个组件项目团队在人力估计就无法支持了。
我们面临三个问题要解决:
1、最小成本开发自定义界面。
2、这个自定义界面可以由外面的团队开发。
3、被复用的组件如发布新版本,不能影响其它团队对其已经开发自定义界面。
解决思路就是:对原来的业务组件(宿主业务组件)进行MVP改造,把MP抽取出来进行复用,V只是很薄一层,纯粹和界面呈现有关(和业务无关),可以由其它团队(扩展业务组件)自定义开发V部分。宿主业务组件和扩展业务组件代码无直接耦合,并且扩展业务组件能复用宿主业务组件中P部分。从这个思路出发,我们设计了如下的架构:
在这个架构中,宿主业务组件和扩展业务组件地位是平等的,都是能被组件管理器管理,宿主组件已经是MVP改造,扩展业务组件中自定义界面通过动态代理机制复用宿主业务组件的业务逻辑。
2.2 类图
在上述类图中,我们可以看到扩展业务组件和宿主业务组件被appfactory(应用工厂框架)给分隔开。扩展业务组件只是实现一个view,并有自己的接口,实现针对自己的接口编程,通过appfactory提供的代理类来进行访问宿主业务组件的presenter能力;宿主业务组件进行MVP改造,支持外部业务组件复用其presenter。
2.3 实现
2.3.1 应用工厂机制简述
由于自定义扩展页面机制是在组件化上面实现,因此这边大概介绍一下组件化相关知识。
组件化开发流程:
1、具体就是各个业务团队把业务代码按照一定规范封装成业务组件。
2、并在一个地方可以定义发布这个业务组件,包括属性,提供页面,事件等。
3、应用配置人员在应用编辑器(颗粒就是业务组件),配置业务组件属性,以及页面之间跳转关系等。
4、打包应用。
5、启动应用,所有的业务组件初始化、页面之间跳转、属性读取、事件分发都是通过appfactory (应用工厂框架)实现。
2.3.1.1 时序图组件化开发时序图:上述图中开发流程:
1、业务开发人员把自己业务包装一个业务组件,并经过QA测试和发布。
2、应用配置人员登录拼装门户后创建产品(如已经创建直接选定)。
3、打开编辑器对应用进行编辑(添加业务组件,修改业务组件属性,配置页面路由等)
4、配置完成保存,触发构建(构建是统一构建服务器)
5、打包完成,QA可以在拼装门户获取包并测试,如测试通过就审核,通知产品经理发布。
应用工厂初始化时序图
上述图中是应用工厂初始化流程:
1、用户打开应用
2、appfactory(应用工厂框架)初始化。
3、协议初始化(我们支持混合开发,有原生协议,H5协议,RN协议)每个协议管理不同类型业务组件生命周期。
4、每种协议初始化会把相应类型业务组件初始化。
5、所有组件初始化完成,框架就会根据配置好的页面路由,打开界面。
启动页面跳转时序图
这个是打开应用页面时序图:
1、打开应用,根据应用配置(如安卓规定的配置首界面)打开欢迎界面。
2、通过Appfactory(应用工厂框架)获取配置的启动页面。
3、然后通过框架启动页面,框架根据页面地址,调用相应的业务组件打开页面。
2.3.2 自定义扩展页面实现
2.3.2.1 各个模块改造点
appfactory 要实现:
1、提供代理帮助类
2、存储宿主业务组件和扩展业务组件页面扩展映射关系。
3、组件生命周期管理和页面跳转路由(这个不是当前叙述重点,这边就不展开)
宿主业务组件要实现:
1、在其业务组件定义中申明该页面支持扩展。
2、要扩展的页面进行MVP改造。
3、在其presenter中获取view的实例改成通过appfactory代理帮助类获取。
4、在其组件页面路由中调用,改为通过appfactory代理帮助类获取真实的页面。
5、公布自定义扩展页面开发文档。
扩展业务组件要实现:
1、在其业务组件业务定义申明中,对宿主组件某个页面扩展。
2、按宿主业务组件提供文档定义IView和IPresenter接口。
3、在扩展组件的自定义页面中获取Presenter实例通过appfactory代理帮助类获取。
2.3.2.2 编辑器配置变更
在配置的时候配置指定扩展页面。在配置产品的时候这边有个变化:
1、假如目前有2个扩展业务组件对微博的默认界面做了扩展。
2、在编辑器的时候,要在微博业务组件默认界面选择对其扩展的页面。
2.3.2.2 页面跳转流程变更
上述是实际页面路由过程
1、假设现在是打开微博页面
2、框架会调用微博业务组件入口类goPage方法,参数携带相关信息。
3、微博业务组件通过框架方法获取扩展页面信息。
4、如扩展业务组件是配置延迟初始化,这时候就把它初始化。
5、微博组件就根据扩展业务组件界面页面信息,启动扩展页面。
2.3.2.3 初始化实例变更
该时序图描述页面路由过程实例创建时序:
1、框架调用宿主业务组件打开界面。
2、宿主业务组件根据传入的参数去代理帮助类获取真实的页面信息。(有可能是扩展页面)
3、代理帮助类最终向扩展业务组件获取真实页面信息
4、宿主业务组件获取真实的页面信息。
5、宿主业务组件调用系统方法创建页面。
6、页面被创建的时候,调用框架代理帮助类获取presenter实例句柄。(自己页面实例作为一个参数)
7、代理帮助类调用宿主业务组件创建presenter实例。
8、presenter在创建过程,根据传入页面对象调用代理帮助类创建IView接口实例。
9、扩展页面获取到IPresenter实例以后就可以调用它处理业务。
2.4 注意事项
1、宿主提供说明文档,直接提供接口定义并去掉包名,这样扩展组件可以直接用,不容易出错。2、宿主在对接口维护,只能增加,不能修改和删除原有已经公布出去的接口。3、宿主在原有接口增加方法,对扩展无影响,扩展组件要视情况决定是否要补齐功能。
(作者: 高级软件开发工程师 苏昌骏)