热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

客户端模块化解耦实践Router

客户端模块化解耦实践–Router背景随着业务进入快速发展期,业务线拓展迅速,项目结构变得庞大复杂,导致迭代的成本越来越高。项目的越发庞大也使得整个工程编译时间越来越长。进行项目拆
客户端模块化解耦实践 – Router

背景

随着业务进入快速发展期,业务线拓展迅速,项目结构变得庞大复杂,导致迭代的成本越来越高。项目的越发庞大也使得整个工程编译时间越来越长。进行项目拆分后模块化运行(项目自运行、自管理)是一个较好的出路,但项目间的复杂相互引用导致我们无从下手,而如何去除这些项目间的引用则是今天的主题

产生

首先我们可以整理下现状,我们的碰到的最大问题其实就是项目间杂乱无章的耦合、引用

《客户端模块化解耦实践 - Router》 actu-structor

针对这些耦合、依赖,我们可以简单的分析:从理论上来说,每个项目负责单独的业务,应该本身就是相互独立的存在。但是实际上,处于同一个公司业务体系中,项目与项目之间不可能真正做到完全的切割。(公司与公司间都可能存在合作,项目与项目间当然就更可能了)

看来想从业务源头想去除业务依赖是不太可能了,我们只能从我们技术角度去进行项目间耦合、引用的去除了。而其中最关键的一点就是:耦合、相互依赖 , 而Router的概念正是为了解决这些问题而提出的。

那Router到底是什么呢?

Router是一个具有收口分发思想的、用于处理模块(项目)间直接依赖、调用的模型。最直接的体现其实就是一个约定、一个规则,按照约定规则进行解析,而后分发。

《客户端模块化解耦实践 - Router》 hope-router

发展

针对以上的一些问题,其实我们能get到一些关键特性,收口分发

OK,既然关键信息已经得到,伪代码很快就能敲出:

public static void jump(String rule){
if(rule){
gotoBusinessActivity()
return;
} if(rule){
invokeBusinessFunction();
return;
}
}

Perfect!!! 调用方便,确实做到了 收口分发

但是随着业务发展,弊端显示的也越发明显。仔细分析过后,可以总结出一些问题

膨胀

  • 随着业务的增长,膨胀的太厉害,成千上万行代码坐落在这个类中,名副其实的大杂烩

维护成本

  • 所有项目均同时维护,一个项目改错可能导致其他项目也受到影响
  • if-else的逻辑维护太复杂
  • 分发逻辑是 hardcode ,一旦错误将无法进行修改
  • 各项目无法真正分离

扩展

  • 从代码结构不难看出,这里的分发实质上是‘亲力亲为’并非真正的分发,而‘亲力亲为’是做不到真正的拆分的

从上面几个问题来看,其实收口并不难,如何做好分发这才是一件难事。

回顾需求与现有的问题,觉得自己的if-else模型其实丢了一件事:没有抽离出一套统一的流程

那流程怎么定义?

我在设计的时候喜欢类比现实生活,而Router也不外乎。而这个模型就很有趣了

我想要寄快递

这个是我需求,那么该怎么办呢?最重要的两点

  1. 地址
  2. 包裹

我只需要给到快递员地址以及包裹,那么就会给我送到(除非地址错了),但是这里要强调一点,我寄快递,并不是快递员来接受这个包裹。快递员只是,而真正接收的是所在地址的人。

想到这里其实就可以梳理出Router的核心生命周期与非核心生命周期了。当Router把调用者的意图转达给接受者时就已经完成了它的周期. 接下来的事就是接受者干的了,跟Router无关

《客户端模块化解耦实践 - Router》 invoke

到这里其实Router的基本框架已经确定(因为流程已经抽离), 那么剩下的就是具体模块的事了。不难分析出Router的两大核心模块:规则处理执行器

规则

通过模型,我们知道了两个必须的东西:地址包裹 ,那对应到程序中无非就是 目标指向数据 ,再契合到我们当前的业务场景,规则不难提炼出来:

protocol://path?data

是不是很眼熟?其实就是我们通用url的简化版本。其实重点就三个信息:

  1. 协议,用于版本区分、功能区分等
  2. 目标指向,路径,意图的接受者
  3. 数据,携带的数据

同样我们的规则定义为

tctclient://project/module?key=value

协议定义完毕了,但是难点来了: Path的映射如何处理

举个例子,Pathhotel/list ,但是这个只是一个字符串,我真正想要调用的是 酒店列表 HotelListAction ,如何将 Path目标指向 进行关联,这是一个问题。在讨论的过程中,其实有三种方案:

  1. if-else 直接逻辑关联
  2. 使用 Map 进行关联
  3. 使用 xml 进行关联

其实从本质上来说,都一样,都是为了数据关联。但是在维护、扩展上是有区别的。

if-else 这个不用说,直接的引用会产生的非常复杂的依赖网,而且无法抽离出分发这块,所有的分发都需要在当前环境下进行关联。

Map & XML的逻辑很像,都是 path处理类 的对应。但是在维护和扩展性上我们最终选择了xml, 优点很明显:无代码耦合可动态更新 。而MAP的优点在于,不需要进行数据加载,节省了这一部分的性能。

执行器

执行器的概念就好理解多了,当一段规则被解析成可读的意图后,我需要将意图、数据传递给接受者,而这个接受者可能是各个业务所处理的。因此我需要设计一个接口(不然没指向性了)来进行接收,而业务部门可通过实现这个接口来进行处理相关逻辑。

《客户端模块化解耦实践 - Router》 flow1

虽说到这里流程基本已经结束,但是在实际的业务逻辑中会衍生出一些特殊的逻辑,比如:

我要打开酒店列表,但是有个要求,必须是登陆状态才可直接进入,否则需要先去登陆。

可能仅仅从这一个需求上来看,用现在的流程去完成也不难,可以使用两个接收者去完成。但是当中间衍生的特殊逻辑很多而且可能多个业务都会需要,那用两个(多个)接收者可能就有会膨胀的很厉害了。那这个时候其实另外一个概念就产生了 — 拦截器

拦截器

拦截器是当一个规则产生最终作用前进行的一些特殊而通用的逻辑处理。

处理逻辑其实很像View Touch事件,当一个Touch事件产生后,首先是分配(分发),然后是拦截,最后才是消费

而我们的拦截器稍有不同的地方是

  1. 拦截器可以多个
  2. 只有当所有拦截器全部满足(pass)的情况下才会走到执行器(消费)
  3. 这种拦截器模型可以通过递归来进行实现

《客户端模块化解耦实践 - Router》 flow-interceptor

拓展

对于Router框架设计到使用到现在已经2年多了,业务需求等也都基本可以完成、满足。也确实做到了Router框架抽离、流程确定且业务逻辑(包括映射关系)各自维护。当一个业务需求产生时,只需要产生一个新的规则来维护即可。

但是重新审视下目前的框架其实仍然有很多需要完善的:

  1. 数据映射是通过xml来映射,那么必然会带来I/O加载的性能开销
  2. 是否项目调用需要写一段很冗长的规则,比如 jump("tctclient://hotel/list"),而这种字符串参数通常是无法通过编译器去检查正确与否的

针对这两个问题,其实我们采用的是(插件)工具去完成。

Gradle Plugin + Freemarker

在编译时进行辅助类的建立

  • xml的加载可以在编译时直接生成Map关系维护。
  • 冗长规则则可以通过枚举来进行维护(枚举通过xml生成), 调用方式则改为jump(Bridge.HOTEL_LIST)

总结

该篇文章更多的写的是我在接受到这个需求时的心路历程,每个人在写代码的时候都有自己的感受。而从设计Router中其实能发现很多道理

没有一个模型放之四海而皆准的

比如一开始的 if-else ,当业务量很小的时候 , 这可能是一个非常好的处理方式。而当业务膨胀,当前结构不适用时需及时重构。

框架脱离了业务那就什么都不是,只有契合业务的框架,才是一个好框架。

思考比编码更重要

当接受到一个需求后,更多的是需要去思考,去设计。提炼出核心关键点、流程,针对流程梳理出核心生命周期非核心生命周期很重要


推荐阅读
  • 采用IKE方式建立IPsec安全隧道
    一、【组网和实验环境】按如上的接口ip先作配置,再作ipsec的相关配置,配置文本见文章最后本文实验采用的交换机是H3C模拟器,下载地址如 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 本文介绍了如何通过设置背景形状来轻松地为 Android 的 TextView 添加圆形边框。我们将详细讲解 XML 代码的配置,包括圆角、描边和填充等属性。 ... [详细]
  • 方法:1 配置数据库basediros.path.abspath(os.path.dirname(__file__))  #获取当前文件的绝对路径appFlask(__name__ ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
  • 本文详细探讨了JavaScript中的作用域链和闭包机制,解释了它们的工作原理及其在实际编程中的应用。通过具体的代码示例,帮助读者更好地理解和掌握这些概念。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • 在进行QT交叉编译时,可能会遇到与目标架构不匹配的宏定义问题。例如,当为ARM或MIPS架构编译时,需要确保使用正确的宏(如QT_ARCH_ARM或QT_ARCH_MIPS),而不是默认的QT_ARCH_I386。本文将详细介绍如何正确配置编译环境以避免此类错误。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 本文深入探讨了Memcached的内存管理机制,特别是其采用的Slab Allocator技术。该技术通过预分配不同大小的内存块来有效解决内存碎片问题,并确保高效的数据存储与检索。文中详细描述了Slab Allocator的工作原理、内存分配流程以及相关的优化策略。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 版本控制工具——Git常用操作(下)
    本文由云+社区发表作者:工程师小熊摘要:上一集我们一起入门学习了git的基本概念和git常用的操作,包括提交和同步代码、使用分支、出现代码冲突的解决办法、紧急保存现场和恢复 ... [详细]
author-avatar
假如有梦想
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有