本文来自 网易云社区 。

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

前言

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

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

模块定义

独立的业务模块

它和组件的区别是:

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

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

模块类图

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

领域驱动设计(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模块化实战--如何设计一个通用性的模块

云课堂Android模块化实战--如何设计一个通用性的模块的更多相关文章

  1. Netty实战:设计一个IM框架

    来源:逅弈逐码 bitchat 是一个基于 Netty 的 IM 即时通讯框架 项目地址:https://github.com/all4you/bitchat 快速开始 bitchat-example ...

  2. 如何在Visual Studio 2017中使用C# 7+语法 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建NetCore应用框架之实战篇系列 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 NetCore入门篇:(十二)在IIS中部署Net Core程序

    如何在Visual Studio 2017中使用C# 7+语法   前言 之前不知看过哪位前辈的博文有点印象C# 7控制台开始支持执行异步方法,然后闲来无事,搞着,搞着没搞出来,然后就写了这篇博文,不 ...

  3. webview之如何设计一个优雅健壮的Android WebView?(下)(转)

    转载:https://iluhcm.com/2018/02/27/design-an-elegant-and-powerful-android-webview-part-two/ (这篇文章写得有点晚 ...

  4. 如何设计一个优雅健壮的Android WebView?(下)

    转:如何设计一个优雅健壮的Android WebView?(下) 前言 在上文<如何设计一个优雅健壮的Android WebView?(上)>中,笔者分析了国内WebView的现状,以及在 ...

  5. Android实训案例(九)——答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程

    Android实训案例(九)--答题系统的思绪,自己设计一个题库的体验,一个思路清晰的答题软件制作过程 项目也是偷师的,决心研究一下数据库.所以写的还是很详细的,各位看官,耐着性子看完,实现结果不重要 ...

  6. webview之如何设计一个优雅健壮的Android WebView?(上)(转)

    转接:https://iluhcm.com/2017/12/10/design-an-elegant-and-powerful-android-webview-part-one/ 前言 Android ...

  7. 如何设计一个优雅健壮的Android WebView?(上)

    转:如何设计一个优雅健壮的Android WebView?(上) 前言 Android应用层的开发有几大模块,其中WebView是最重要的模块之一.网上能够搜索到的WebView资料可谓寥寥,Gith ...

  8. Android 设计一个菱形形状的Imageview组件.

    网上没有资料,特来请教下大神 Android 设计一个菱形形状的Imageview组件. >> android这个答案描述的挺清楚的:http://www.goodpm.net/postr ...

  9. Android学习笔记(十二)——实战:制作一个聊天界面

    //此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! 运用简单的布局知识,我们可以来尝试制作一个聊天界面. 一.制作 Nine-Patch 图片 : Nine-Pa ...

随机推荐

  1. 配置git 环境变量

    1.从Git官网下载windows版本的git:http://git-scm.com/downloads 2.一般使用默认设置即可:一路next,git安装完毕! 3.但是如果这时你打开windows ...

  2. CentOS 安装python3.5

    1.刚开始centos可能会缺少gcc等组件,先安装组件 yum groupinstall "Development Tools" 2.下载源码,解压后进入目录 #下载地址http ...

  3. Git(三):Git 使用规范流程

    转:http://www.ruanyifeng.com/blog/2015/08/git-use-process.html 团队开发中,遵循一个合理.清晰的Git使用流程,是非常重要的. 否则,每个人 ...

  4. UGUI BUG

    UNITY UGUI问题:父类使用 GroupLayout,子类使用contentsize filter时,会出现运行时布局重叠,但隐藏后再显示就会好了.

  5. SPI子系统分析之四:驱动模块

    内核版本:3.9.5 SPI控制器层(平台相关) 上一节讲了SPI核心层的注册和匹配函数,它是平台无关的.正是在核心层抽象了SPI控制器层的相同部分然后提供了统一的API给SPI设备层来使用.我们这一 ...

  6. Mac 通过gem安装CocoaPods及Pod的使用

    注:根据http://www.jianshu.com/p/6e5c0f78200a的文章做了部分修改 一.什么是CocoaPods CocoaPods是iOS项目的依赖管理工具,该项目源码在Githu ...

  7. Gouraud Shading

    [Gouraud Shading] Gouraud Shading (高洛德着色/高氏着色) 这种着色的效果要好得多,也是在游戏中使用最广泛的一种着色方式.它可对3D模型各顶点的颜色进行平滑.融合处理 ...

  8. Apache Hive (二)Hive安装

    转自:https://www.cnblogs.com/qingyunzong/p/8708057.html Hive的下载 下载地址http://mirrors.hust.edu.cn/apache/ ...

  9. 【HDU4967】Handling the Past

    题意 模拟栈操作.有三种操作push,pop,peak分别代表从栈顶压入元素,删除栈顶元素,查询栈顶元素.但是,每个操作会给出一个时间戳,要求操作必须要按照时间戳来进行.但是对于每个peak必须马上给 ...

  10. zabbix自定义key监控nginx和fpm(网站并发数)

    一. nginx编译参数 监控nginx,主要讲解监控并发数 --prefix=/usr/local/nginx --with-http_stub_status_module zabbix编译参数的查 ...