本文来自 网易云社区 。

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

前言

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

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

模块定义

独立的业务模块

它和组件的区别是:

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

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

模块类图

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

领域驱动设计(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. MyBatis初识(通过小实例清晰认识MyBatis)

    1.MyBatis框架: MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可 ...

  2. golang的array/slice

    相同点 由相同类型的元素组合构成 元素有序排列,0为第一个元素下标 基本使用方法相同 区别 array声明时需要指定容量大小,而且无法修改 slice可通过append增加元素,当容量不够时,会自动扩 ...

  3. Spark之 Spark Streaming流式处理

    SparkStreaming Spark Streaming类似于Apache Storm,用于流式数据的处理.Spark Streaming有高吞吐量和容错能力强等特点.Spark Streamin ...

  4. listener does not currently know of SID given in connect descriptor

    一次连接数据库怎么也连接不上,查了多方面资料,终于找到答案,总结 首先应该保证数据库的服务启动 在myeclipse的数据库视图中点 右键->new 弹出database driver的窗口, ...

  5. 136. Single Number唯一的数字

    [抄题]: Given an array of integers, every element appears twice except for one. Find that single one. ...

  6. ”$-”与shell默认选项

    一.前言 之所以整理这篇博客,主要是写Linux环境设置文件 的时候,在查看/etc/profile时看到这么一段代码: for i in /etc/profile.d/*.sh ; do if [ ...

  7. asp.net webform过滤器(注意我们可以在拦截请求的同时设置回调函数)

    .过滤器代码 public class PageFilter : IHttpModule { public String ModuleName { get { return "PageFil ...

  8. HBase表的memstore与集群memstore

    一直有一个问题,今天调查了一下源码算是明白了. ===问题=== 通过java api(如下代码所示)在创建表的时候,可以通过setMemStoreFlushSize函数来指定memstore的大小, ...

  9. JS实现windows.open打开窗口并居中

    function openWin() {            var url='Add.aspx';                             //转向网页的地址;           ...

  10. win10 64位 python3.6 django1.11 MysqlDB No module named 'MySQLdb' 安装MysqlDB报错 Microsoft Visual C++ 14.0 is required

    在python3.6中操作数据库,再按python2.7安装MySQLdb进行数据库连接已经不可用了,我使用的是另外一个方法:PyMySQL,安装好之后还是不能直接连接MySQL的,启动项目后报No ...