前端如何实现业务解耦,携程酒店查询首页的1.0到3.0

256
原文链接: mp.weixin.qq.com

作者简介

何金,携程酒店研发部Android资深软件开发工程师,负责酒店代码性能优化、结构改造、疑难问题排查处理,以及Kotlin的推广和应用。

酒店查询首页,是用户使用携程APP进行酒店预订的第一个页面。它提供了各种类型的酒店筛选入口,让用户进行酒店选择。随着查询首页版本不断迭代,其对应业务,功能和样式经历了由简单到复杂,单一到丰富的过程。

为了更好的适应业务的快速迭代,查询首页的结构也经历了多个版本优化和重构。本文将分享携程酒店是如何根据查询首页自身业务需求特点,进行代码结构优化和重构的。

根据查询首页不同时期业务和代码结构特点,简单的把结构迭代版本划分为三个版本。分别对应简单的1.0,头疼的2.0,合适的3.0。

1.0

1.0版本是酒店查询首页最早期的一个版本,它所包含的业务可以简单的由上到下做垂直划分,顶部的标题模块,中间提供给用户进行输入的查询模块,以及酒店底部推荐模块。如下图所示:

 

根据这种业务特点,将不同业务模块抽象为不同的Fragment。将这些Fragment统一放到ScrollView中进行布局,InuqireCacheBean用来管理Fragment的数据,具体的结构如下图:

从上图看,1.0采用的是典型的MVC模式。结构清晰明了,但是业务逻辑代码和样式布局全部耦合在相应的Fragment里。如果一直维持这个结构,那么随着业务不断迭代和增加,Fragment里面的代码会越来越臃肿,业务的实现成本和排查问题的难度都会越来越大。

2.0

下面的三张贴图是2.0版本时期新增的一些业务所对应的样式。

 

        

2.0版本查询页的功能和样式比1.0都要丰富和复杂很多。根据这种特点,对查询页进行了模块化拆分,把不同的业务抽象成对应的Moudle,通过Module管理自身业务和UI布局。

      

      

上图是查询首页2.0的架构图,它的容器仍然是HotelInquireActvity,通过ScrollView

管理TitleFragment, InquireCoreFragment,RecommendFragment和BottomFragment。

BottomFragment是2.0新增的一个Fragment,它目的是展示和管理查询首页底部新增的Moudle。Module的设计采用MVP模式, BizMoudle代表V层,会向外暴露一个getView接口,用来展示该业务的样式,BizMoudlePresenter代表P层,用于处理业务逻辑,BizMoudleRepositery代表M层,用于处理数据。Module的结构如下图:

 

InquireBizConfig是各个业务Module配置管理器,用于管理加载不同业务对应Module。InquireBizConfig由两部分组成,一是BizConfig,二是BizManager。

BizConfig内部核心是一个Map数据结构,用于注册查询首页需要用到的所有Module,BizManager内部核心是一个List数据结构,用于加载和定义各个业务Moudle的展示位置顺序。

当BottomFragment加载启动的时候,会调用BizManager里面的startLoadMoudles()方法,这个方法会遍历List,取出相应的Moudle加载到BottomFragment中,它的结构如下图所示。

 

从2.0架构图看,对查询首页业务进行了模块化拆分,可以方便团队中不同业务的开发Owner进行同时开发,减少了相互的干扰,提高了业务需求的交付效率,但美中不足的是结构没有做到统一。

有的业务以Fragment形式存在,有的业务以Module形式存在。该结构还会存在性能黑洞,当BottomFragment启动时,会加载所有配置的Moudle,把所有Moudle的View加载到布局容器ScrollView中,无论这些Moudle是否在第一屏展示,影响查询首页的启动性能。

另外由于布局容器采用的ScrollView,如果业务Module里面采用了ListView控件,EditText控件等,那开发必须使用额外的逻辑去处理ScrollView和这些控件带来的兼容性问题。布局容器采用的ScrollView,带来的交互实现成本也很高。

3.0

 

为了解决2.0结构存在的问题,我们又进行了3.0版本迭代。

3.0版本主要围绕2.0版本存在的两个问题,一是根布局使用ScrollView带来的性能和兼容性问题;二是结构没有统一,业务分别以Module和Fragment形式存在的问题。

针对ScrollView产生的问题,分别选择了三种可替代ScrollView的方案。

第一种是使用RecyclerView, 通过Type来区别各个业务对应的布局,这个方案是理论上行的通的,但是需要改造各个Moudle对应的数据结构,需要将不用业务的数据结构进行结构统一,改造成本太高,实现起来难度和风险较高。

第二种采用酒店内部控件GroupListView控件,该控件的核心是基于ListView,它是将不同的Adapter融合到一个Adapter中。

第三种方案是使用开源组件Vlayout,该控件的实质是异构的RecylerView,原理和GroupListView相似。

对比第二种和第三种方案,RecyclerView解决了ListView不能局部刷新的问题,另外RecyclerView设计上采用四级缓存,在性能上也比ListView更优,可为查询首页将来支持流畅性较高的复杂交互做好准备。

基于以上考虑,选择第三种方案替代ScrollView。对之前2.0存在的TitleFrament,InquireCoreFragment,ReCommendFragment进行模块化改造,分别改成为TitleModule,InquireCoreModule,ReCommendModule,移除2.0作为Mudule容器的BottomFragment。经过改造后,3.0的结构图如下:

  

经过了3.0版本的结构迭代和优化,所有的模块都统一抽象为了Moudle,结构变得更加清晰简单。采用Vlayout替代ScrollView,解决了2.0结构版本中潜在的性能黑洞问题,同时Vlayout提供了大量的UI模板,避免了前端重复造轮子,提高了业务交付效率。

小结

酒店查询首页经过三个版本迭代和优化,结构趋于稳定,为查询首页的未来业务快速交付和生产环境稳定打下了良好基础。

在对酒店查询首页优化和改造时,根据我们团队的特点,采用了渐进式的架构迭代思路,这样既保证了业务需求的交付,也避免了重构带来的风险。

同时也给前端如何实现业务解耦,如何在保证页面性能的前提条件下,承载大量不同的UI布局元素提供一种优化借鉴思路。

【推荐阅读】