模块化与解耦

简述

本文主要讲述了在iOS开发过程中,模块化工程架构的一种组织方式,本文主要讲述基于cocoapods来做模块化的方案,详细讲述了iOS开发怎么进行模块划分的内容,主要会在以下方面做阐述:

  • 为什么要做模块化
  • 模块设计原则
  • 模块化开发有哪些优点和缺点
  • 解耦与通信

1.为什么要做模块化?

我们都知道最基本的代码设计原则:“Don’t repeat yourself!”,每一个工程都会有自己的架构,即使你是刚入门的开发者,写几天代码也会发现要把一些常用到的重复代码单独拿出来放在一个叫common的地方,实现代码复用。这样看来每个开发者其实都或多或少的做过架构方面的事情,每个团队至少有1~2个人在做这样的事情。

说道app代码架构,记得Samurai的开发者郭虹宇在群里说过这段精辟的话,引用一下:

一派是说app开发并不需要什么狗P架构,第二派说我们有自己NB的架构,第三派说只要模块化够好,每个模块应该有自己的架构。

这三个观点的出发点,我觉得也比较好理解,第一种应该是一些个人开发者,个人能力很强,经常一个人很快搞出来一个app,他的映像中不需要弄太多的框框框住自己,但是其实他也是有一套自己的架构的。第二派应该是一些公司或者大公司,有一套NB的架构对于团队的意义就比较大了,可以保证稳定迭代,保证规范和持久可维护性。第三派应该是BAT这样的有很多BU的超级公司,或者一些先进的开源开发者们,模块化能够更好的实现跨app的代码和功能的复用, 能够更好的共享资源,避免重复造轮子。

那么为什么要做模块化?已经很明显了,模块化的代码框架最屌,不信,看看苹果的框架怎么做的,你就明白了。

2. 模块设计原则

既然模块化最屌,那怎么才能做好project的模块化拆分呢,哪些代码应该被放到一个模块?这里分享一些我的经验。

越底层的模块,应该越稳定,越抽象,越具有高复用度。

这一点,目测大家应该比较认同,越是底层的SDK,就应该越稳定,稳定的最直观表现就是API很久都不用变化,所有的变化因子不要暴露出来,避免传递给依赖它的模块。但是要做到设计一套API很久都不用改变,那么就需要设计的时候能越抽象, 即需要我们抽象总结的能力。

稳定性 还有一个特点就是会传递,比如 B 模块依赖了 A 模块,如果 B 模块很稳定,但是 A 模块不稳定,那么B模块也会变的不稳定了,因此下一个原则:

不要让稳定的模块依赖不稳定的模块, 减少依赖

既然上面说最好不要依赖,但是我发现我的 B 模块的确依赖了 A 模块里面不可或缺的代码怎么办? 假设依赖的代码段为 x , 现在来看x的特性, 如果X是一个可能高复用的代码段,那么无妨把x从 A 模块里面拿出来,单做成一个模块 X, 那么 B 模块依赖 X 模块就好了;灵一种情况,x是一个方法或函数,而且不太适合单做成一个模块,所以那就在B模块里面拷贝一份 x 代码就ok了,因为这样可以保证模块的 稳定性自完备性.

如果上面两种方法都不太合适,我们会在后面解耦里面讲到如何解耦

提升模块的复用度,自完备性有时候要优于代码复用

什么是自完备性,就是尽可能的依赖少的模块来达到代码可复用。

举个例子,我有个模块 Utils 里面放了大量的category工具方法等,在日常UI产品开发中,依赖这个Utils会很方便,但是我现在要写一个比较基础的模块,应该就要求复用度更高一些,这个时候需要用到Utils里面的几个方法,那这个时候还适合直接依赖Utils吗,当然不合适了,这与我们上面的设计原则相悖了啊,因此我们这时候为了这个模块的自完备性,就可以重新实现下这几个方法,而不是依赖Utils模块

每个模块只做好一件事情,不要让Common出现

模块化结构是让工程结构更清晰,每个模块都只做一件事情,都有自己的一个命名,这样这个模块才能良性发展, 但是这个名字千万不要再叫Common了,试想下你有没有做过这样的事情:“哎呀,这块代码放哪都不太合适,放Common吧”, 日久以后,这个Common就变成了毒瘤,大家都依赖它,还一堆不相关的代码,这个Common模块就是我们设计原则第一点的反面教材: “非常不稳定,大量依赖,全是耦合,整个模块无法复用到其他app”, 所以删掉工程里面的Common吧,再遇到不知道放哪的代码,就要好好思考模块的设计,再不行如果具有可复用性就单建一个模块吧,为什么不可以呢?

按照你架构的层数从上到下依赖,不要出现下层模块依赖上层模块的现象

业务模块之间也尽量不要耦合

3. 模块化开发有哪些优点和缺点

优点: 1、不只提高了代码的复用度,还可以实现真正的功能复用,比如同样的功能模块如果实现了自完备性,可以在多个app中复用 2、业务隔离,跨团队开发代码控制和版本风险控制的实现 3、模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力。

缺点,模块化当然也有它的缺点: 1、入门门槛较高,新手入门需要的成本也更高 2、工具的使用成本,团队间和模块间的配合成本升高,开发效率短期会降低。

但是从长期的影响来说,带来的好处远大于坏处的,因此模块化仍然是最佳的架构选择。

4. 解耦与通信

我先说说为什么要解耦吧,模块化并不是说你把工程的代码拆分成 50 个 pod 或者framework就算完事了,要实现模块之间真正的解耦才算真正的模块化,否则如果模块之间还都是互相调用代码,循环依赖,那么和原本放文件夹里面没啥两样。那么什么是模块间的解耦呢?

模块解耦的目标就是, 在基于模块设计原则上, 让模块之间没有循环依赖, 让业务模块之间解除依赖。

4.1 公共模块下沉

这块其实还是讲的模块设计,一个工程的架构可能会分为很多层,然而在开发的过程中,很容易有人不注意让应该处于较底层的模块依赖了上层的模块,这种情况下应该对模块的设计进行改造实现单向依赖。

比如一个常见的普遍的例子: 一个公共的WebView模块,里面可能有WebViewController的基类,然后还有JSBridge的服务,如果设计的时候没有注意,很容易在开发过程中,这个模块被塞入大量的其他业务代码,依赖了一大堆业务模块,因为经常注册JSBridge服务需要跟业务耦合。

这个时候怎么做呢,首先我们要思考WebView模块的定位,从更全局的角度思考,每个app的架构应该都需要这样一个模块,那么我们完全可以把这个模块单独拎出来下沉为基础模块,这个时候的解耦就需要你对WebView模块做出一些设计,添加一些注册型Api,修改JSBridge的服务为可以通过注册的方式添加逻辑,这样来实现与业务解耦,业务完全可以把与自己业务相关的代码放在自己的模块里面,然后通过你设计的Api注册到WebView模块中。

4.2 面向接口调用

虽然说公共模块可以通过架构设计来避免耦合业务,但是业务模块之间还是会有耦合的啊,而且这种情况是最多的,比如页面跳转啊,数据传递啊,这些情况前面的方法已经不够用了。那如何解耦不同业务模块之间的代码调用呢?

那就是面向接口调用,我们知道只要直接引用代码,就会有依赖,比如:

 // A 模块
- (void)getSomeDataFromB {
    B.getSomeData();
}

// B 模块
- (void)getSomeData {
    return self.data;
}

 

那么我们可以实现一个 getSomeDataFromB 的接口,让 A 只依赖这个接口,而 B 来实现这个接口,这样就实现了 A 与 B 的解耦。

// 接口
@protocol BService <NSObject>
- (void)getSomeData;
@end

// A 模块, 只依赖接口
- (void)getSomeDataFromB {
    id  b = findService(@protocol(BService));
    b.getSomeData;
}

// B 模块,实现BService接口
@interface B : NSObject <BService>

- (void)getSomeData {
    return self.data;
}

@end

这样就可以实现了即满足了模块之间调用,也实现了解耦

优点:

1、接口类似代码,可以非常灵活的定义函数和回调等。

缺点:

1、接口定义文件需要放在一个模块以供依赖,但是这个模块不回贡献代码,所以还好。
2、使用较为麻烦,每各调用都需要定义一个service,并实现, 对于一些具有普适性规律的场景不太合适,比如页面统一跳转

4.3 面向协议调用

面向接口调用的缺点导致并不能满足所有的需求,也解耦的不够彻底,那么终极手段就是通过定义一套协议来实现模块间的通信,协议现成的,那就是URL跳转协议,基本满足需要,简单易上手,基本上现在很多的App架构里面都会有“统一跳转” 这一套东西的,这个不光是对模块解耦有帮助,对于统一化运营都是有极好的帮助的,比如app里面的任何页面,或者任何操作都是通过一个URL来唤起的话,这样是不是就把各个复杂的业务之间解耦了呢,通信都使用URL.

5. 源码推荐

说了这么多,也要放点干货吧,下面给出2个库的介绍,对你模块化的进程希望有帮助。

1、 JLRoutes 是一个URL跳转协议支持的库,跟我想要的简直很契合,强烈推荐。

2、 我自己写的一个解耦框架 AppLord. 简单介绍一下几个概念

* Module 是负责管理启动模块的工具,可以帮助你把AppDelegate里面一坨初始化的代码分别放到不同的module里面去
* Service 就是对上面 4.2 中面向接口解耦方式的一种封装
* Task, 全局的后台任务管理器,有时候一些不知道放哪的任务执行可以塞进去
时间: 2024-08-31 07:15:21

模块化与解耦的相关文章

Spring Cloud 接入 EDAS 之全链路跟踪

在上一篇 Spring Cloud 接入 EDAS 之服务发现篇 中已经讲解了如何接入 EDAS 的服务注册中心.在这一篇中,我们将聊聊全链路跟踪. 全链路跟踪的必要性 微服务框架带来的好处已经无需多言,模块化,解耦,提高了开发的效率,服务具备更好的扩展性等等.但是微服务其实是一把双刃剑,微服务同时也带来了一些问题. 随着业务的拆分越来越细,模块越来越多,一个看似简单的业务动作,很可能跨越了几十个系统,调用链非常复杂. 基于微服务体系之下构建的业务系统存在的问题基本上分为四类. 故障定位难 即使

解读2015之容器篇:扩张与进化

编者按:2015年 的容器技术圈,是各家施展手脚封疆划土的扩张一年,也是 Docker 以及容器技术生态参与者不断完善自己的进化一年.本文作者张磊,浙江大学 VLIS/SEL 实验室云计算团队负责人,Kubernetes 项目 Collaborator.本文由 36 氪转载自微信公众号infoQ(微信号:infoqchina). 回顾 2015年 容器技术的发展历程,我们可以用两个关键词来概括:扩张与进化. 如果说 2014年 仅仅是 Docker 为主的容器技术在云计算以及 DevOps 圈初

Android开发软件架构思考以及经验总结

一.萌芽 作为一只编程经验并不怎么丰富的程序猿来讲,我一直觉得架构师是一个比较神秘的职业,架构设计就更加的高大上了.经过今年的几个项目,之前曾发文叙述我的从MVC到MVP项目重构实战经验,也曾说过我准备对目前手底下的项目进行重构.但是,前段时间,我改变了我的想法.开发模式的重构,仅仅只是换了一个套路,也许在重构的过程中对业务的逻辑进行了一次梳理,也是在基于前人的代码设计上进行了一些优化.但是,这远远还不够,这不是我理想中的开发场景.在项目开发的过程中,也发现存在许多的问题,但是都是一些零散的问题

如何将「插件化」接入到项目之中?

本期移动开发精英社群讨论的主题是「插件化」,上网查了一下,发现一篇 CSDN 博主写的文章<Android 使用动态加载框架DL进行插件化开发>.此处引用原作者的话: 随着应用的不断迭代,应用的体积不断增大,项目越来越臃肿,冗余增加.项目新功能的添加,无法确定与用户匹配性,发生严重异常往往牵一发而动全身,只能紧急发布补丁版本,强制用户进行更新.结果频繁的更新,反而容易降低用户使用黏性,或者是公司业务的不断发展,同系的应用越来越多,传统方式需要通过用户量最大的主项目进行引导下载并安装.怎么办?这

在Python编程过程中用单元测试法调试代码的介绍_python

对于程序开发新手来说,一个最常见的困惑是测试的主题.他们隐约觉得"单元测试"是很好的,而且他们也应该做单元测试.但他们却不懂这个词的真正含义.如果这听起来像是在说你,不要怕!在这篇文章中,我将介绍什么是单元测试,为什么它有用,以及如何对Python的代码进行单元测试. 什么是测试? 在讨论为什么测试很有用.怎样进行测试之前,让我们先花几分钟来定义一下"单元测试"究竟是什么.在一般的编程术语中,"测试"指的是通过编写可以调用的代码(独立于你实际应用

模块化Java:声明式模块化

前一篇文章,<模块化Java: 动态模块化>描述了如何通过使用服务 (service)给应用程序带来动态模块化特性.它们是通过输出的一个(或多个 )可以在运行时被动态发现的接口而实现的.尽管这种方式使得client和server 完全解耦,但是又带来一个如何(何时)启动服务的问题. 启动顺序 在彻头彻尾的动态系统里,服务不仅可以在系统运行的时候装卸,还可以以 不同的顺序启动.有时,这是个大问题:无论A和B的启动顺序如何,在系统达到 就绪状态并准备好接收事件之前,如果没有事件(或线程)出现,那么

JavaScript的模块化:封装(闭包),继承(原型) 介绍

在复杂的逻辑下, JavaScript 需要被模块化,模块需要封装起来,只留下供外界调用的接口.闭包是 JavaScript 中实现模块封装的关键,也是很多初学者难以理解的要点   虽然 JavaScript 天生就是一副随随便便的样子,但是随着浏览器能够完成的事情越来越多,这门语言也也越来越经常地摆出正襟危坐的架势.在复杂的逻辑下, JavaScript 需要被模块化,模块需要封装起来,只留下供外界调用的接口.闭包是 JavaScript 中实现模块封装的关键,也是很多初学者难以理解的要点.最

如何创建高度模块化的 Android 应用

本文讲的是如何创建高度模块化的 Android 应用, "单一职责原则规定,每个模块或类应该对软件提供的某单一功能负责."(en.wikipedia.org/wiki/Single_responsibility_principle) Android 中构建 UI 的职责通常委派给一个类(比如 Activity.Fragment 或 View/Presenter).这通常涉及到以下任务: 填充 View(xml 布局) View 配置(运行时参数.布局管理.适配) 数据源连接(DB 或者

依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦(转good)

依赖注入(DI)有助于应用对象之间的解耦,而面向切面编程(AOP)有助于横切关注点与所影响的对象之间的解耦.所谓横切关注点,即影响应用多处的功能,这些功能各个应用模块都需要,但又不是其主要关注点,常见的横切关注点有日志.事务和安全等. 将横切关注点抽离形成独立的类,即形成了切面.切面主要由切点和通知构成,通知定义了切面是什么,以及何时执行何种操作:切点定义了在何处执行通知定义的操作. http://ju.outofmemory.cn/entry/216839 引子: AOP(面向方面编程:Asp