zoukankan      html  css  js  c++  java
  • 云课堂Android模块化实战--如何设计一个通用性的模块

    本文来自 网易云社区 。

    如何设计一个通用性的模块

    前言

    每个开发者都会知道,随着项目的开发,会发现业务在不断壮大,产品线越来越丰富,而留给开发的时间却一直有限,在有限的时间,尽快完成某个功能的迭代。因此为了减少开发成本,保证业务功能复用,我们会将一些业务独立出来,比如直播间、消息等,做成单独的模块。所以想必都会都模块化开发有所了解。

    本文的目的,并不是讲述如何处理模块化后的每个模块之间的通信问题,以及整个应用的架构问题,而是对于做了这么多模块后,对模块有个总结,在需要创建一个新的模块的时候,可以少走弯路,如何设计一个通用性的模块

    模块定义

    独立的业务模块

    它和组件的区别是:

    • 组件:指的是单一的功能组件,如地图组件、支付组件、路由组件、分享组件等等。
    • 模块:指的是独立的业务模块,如直播间模块、消息模块、课程详情模块、课时学习模块等等。

    模块可以依赖一些组件,模块与模块之间应该不要有依赖关系。

    模块类图

    在开始讲述模块的结构前,先看下完整的模块类图,分为内部元素,外部元素以及外部实现。 内部元素,只能在模块内部流动。 外部元素,在模块内部和外部之间流通的。 外部实现,这些类需要在模块外部定义。

    领域驱动设计(DDD)的几个基本概念

    在讲述模块内的元素之间,先了解几个DDD钟的基本概念:

    • 实体 当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。
    • 值对象 当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。
    • 聚合根 Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。
    • 领域服务 一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。

    模块元素

    • XXXModuleClient 向外提供服务的类,提供静态方法。
    • ModuleInstance 整个模块的最重要的类,模块使用前需要初始化它。
    • ModuleConfig 模块功能的配置项,是否需要打开某个功能等等。
    • ModuleDependency 模块的一些依赖项,就是模块自身无法完成的功能,或者模块内并不关心这个功能。如对于直播间来说,它并不知道参加课程这些逻辑,这些逻辑应该是课程模块所关心的。
    • LaunchData 针对每次服务可配置的选项。
    • UI视图 如Box,它只关心自己的行为,以及用于向页面展示的数据。
    • ActivityOfFragment 一些Box的组装者,负责将领域模型适配成视图模型,塞给Box。
    • ILogic 所有的业务逻辑都发生在这里,它可以调用具体的服务完成业务逻辑,也可以交给领域模型实现。
    • 领域模型 针对这个模块所抽象出来的数据模型,它不是简单get、set,还包含了业务逻辑。ILogic中可以引用住聚合根,其他的实体不应该被引用住。这样不对导致对象的引用散布在各处。
    • DataSource 数据来源,可以从数据库,也可以从服务器。最终要转化成对应的领域模型。
    • 服务对象 单个领域模型无法处理的逻辑一般会交给服务对象。

    模块初始化

    最开始能想到的便是,在应用启动的时候便对模块进行初始化,这样做的好处,简单、快捷。但是增加了应用启动耗费的时间,也增加了应用的内存。

    更为通用常见的方式是,懒加载,在模块用到的时候在进行初始化。

    因为模块的核心类是ModuleInstance,所有的服务都是靠它实现的。因此,模块初始化也就是这个单例对象的初始化。这样在需要模块服务的时候通过获取这个单例,如果单例未创建,则进行初始化即可。

    在模块初始化的时候,我们将初始化的流程通过一个接口,暴露给第三方使用着,使其可以参与到模块初始化流程。

    定义一个ModuleConfigAndDependency接口,提供两个回调方法。

    public interface ModuleConfigAndDependency {
        //自定义修改模块config配置
        void applyConfig(Builder builder);
    
        //自定义配置模块依赖
        void applyDependency(Builder builder);
    }
    

    在调用ModuleInstance构造器之间,先创建一个Builder,通过解析一个双方约定好的配置文件,获取ModuleConfigAndDependency的实现类的名字,通过反射创建对象。将构建ModuleInstance的Builder传递出去,完成自定义配置。在Android中,约定的配置文件可以写在清单文件中。

    模块的配置和依赖

    模块的配置分为两种,一种是模块初始化完成后,配置就定了。另一种,是每次启动服务时的配置。两种的区别在于,作用域不同。一个针对全局的,一个针对每次服务。

    全局的通过模块初始化的时候,修改Builder。

    针对每次服务的,通过LaunchData配置。 在ModuleInstance中有一个存放LaunchData的Map。

    依赖和配置类似,这里就不赘述了。

    模块的领域模型

    为什么需要自己的领域模型呢?

    • 既然叫做领域,那它关注的重心就是自身模块的业务,然后我们可以将一些业务逻辑下沉到领域模型中。
    • 与后端的隔离,有时候移动端与后端的开发可能是并行的,也可能移动端早于后端。那么接口的DTO会频繁的变动,移动端开发不得不去配合他们,影响自身的开发效率。如果移动端做好这层隔离,那么上层业务的开发完全不会受影响,只需要修改DataSource这层即可。

    领域模型的构建来自DataSource,可以从数据库、也可以从服务器,最终的传递给上层的数据必须是领域模型。

    模块的视图/View层

    展现给用户的是由一块块区域组合而成的,我们将这些区域称之为Box,Box所关心的就只有两个,一个是用户行为,一个是视图模型

    • 对于行为,Box只是触发行为,比如点击,比如曝光,而具体的执行逻辑,交给外部实现。
    • 对于视图模型,Box只是将其展现出来,如文字。

    模块的控制层/Presenter

    之前View与数据之间会有耦合性,现在交给Logic来解耦。Logic可以通过定义接口的形式调用View的变化,也可以通过Message的形式通知。而View通过ILogic定义的接口来获取需要的数据,以及逻辑。

    模块的边界

    每个模块都有自己的上下文,因而少不了模块与外部之间对象转换。 因此需要定义这样一层隔离机制,来完成模块内的上下文与模块外的上下文之间的流通。

    可以是共享内核的形式,两个上下文依赖部分共享的模型。这种方式,模块可能要依赖一些通用Model模块。 可以是防腐层的形式,一个上下文通过一些适配和转换与另一个上下文交互。这种方式需要在模块内定义一些简单的值对象。

    所以我们规定模块内的类访问权限,对于使用者,它所能访问的有以下:

    • 暴露给使用者的服务接口
    • 用于方法调用时的依赖对象类

    总结

    好的模块设计,应该是奔着可复用,高内聚,低耦合的方式。最重要的是灵活,可配置,易于扩展。

    以上是最近一年开发,对于模块设计的理解及总结。如有不足不对的地方,请多多指点~

    本文已由作者陈柏宁授权网易云社区发布,原文链接:云课堂Android模块化实战--如何设计一个通用性的模块

  • 相关阅读:
    洛谷 1339 最短路
    洛谷 1330 封锁阳光大学 图论 二分图染色
    洛谷 1262 间谍网络 Tarjan 图论
    洛谷 1373 dp 小a和uim之大逃离 良心题解
    洛谷 1972 莫队
    洛谷 2158 数论 打表 欧拉函数
    洛谷 1414 数论 分解因数 水题
    蒟蒻的省选复习(不如说是noip普及组复习)————连载中
    关于筛法
    关于整数划分的几类问题
  • 原文地址:https://www.cnblogs.com/163yun/p/9121724.html
Copyright © 2011-2022 走看看