软件工程里面, 有高内聚低耦合的概念.
那么, 什么是内聚? 什么是耦合呢?
内聚
所谓内聚, 就是指一个功能模块内所有内容之间的关联度, 相关性和紧密程度.
模块之内的方法, 逻辑, 语义以及其他成员之间, 都有着很强的关联性, 这就是高内聚.
一个好的程序设计必然追求高内聚, 模块内的所有内容必须有着很强的关联性, 它们才能被放在一个模块之内.
举个例子: 设计一个用户模块, 设计者一定是将用户相关的内容放在一个模块之内, 不可能将毫无关联或者关联度极低的 "新闻相关" 内容放到用户模块里.
这就是一种对高内聚的追求, 软件系统只有尽可能达到高内聚, 才更有利于管理, 维护, 扩展.
耦合
所谓耦合, 就是指不同功能模块之间的依赖关系.
比如, 用户模块的所有功能, 都不依赖于新闻模块, 就算没有新闻模块, 用户模块也能独立运行, 这就是用户模块和新闻模块之间没有耦合.
但反之, 如果用户模块依赖于新闻模块, 那么, 用户模块就没办法脱离新闻模块独立运行, 当新闻模块有些东西发生改动的时候, 说不定用户模块也需要改动才能正常运行, 这就是耦合.
高内聚低耦合
软件开发上, 从前期设计, 规划, 到后期开发实现, 都应该秉承高内聚低耦合的思想.
只有尽可能地达到高内聚低耦合, 程序才能更利于维护, 拓展, 从方方面面降低此程序的管理成本.
关于高内聚低耦合的思想, 在充分理解内聚和耦合的概念后, 再有一段时间的软件设计开发, 回过头来想想, 你就会发现这真的是真理.
这里要提一点, 之所以要说 "高" 内聚 "低" 耦合, 是因为现实中, 通常不存在绝对内聚和彻底无耦合.
一个软件, 本身就是由一个个功能模块组成, 这些功能模块一起配合着完成软件的各种功能.
既然存在配合, 那便必然存在耦合.
比如, 你想要实现一个登陆注册功能, 你会用到数据库模块, 加密模块等.
这时候, 你的登录注册模块必然依赖于数据库模块和加密模块.
所以, 耦合在实际项目中是必不可少的.
另外, 我们前面所说的 "模块", 粒度也是不确定的.
比如, 你可以把 "模块" 精确到一个类, 那么你可能有登录模块, 注册模块, 你也可以把模块放大到分类, 如前台, 后台.
所以, 高内聚低耦合的思想是从宏观的架构开始, 一直贯彻到具体的代码文件的. 理解这一点, 非常重要.
我们常用的许多设计模式, 编程范式, 大多都是为了尽可能地实现 "高内聚低耦合".
我的思考
我认为: 内聚过高, 耦合必然升高, 耦合降到最低, 内聚也必然降低.
为什么这么说?
因为: 想要内聚性达到最高, 那就只在模块内放最单一的内容, 没有其它内容与他关联, 只有他自己, 那关联度不用说也是最高的.
但是, 这也导致了这个模块必然过分地依赖于外部模块, 产生了极高的耦合.
因为内聚提高的同时, 代表着功能变得越来越单一, 越来越少, 对外界的依赖就越大.
而耦合如果降到最低呢?
那各个模块之间如果彻底没有了互相依赖, 那每个模块都能独立运行, 每个模块都全能了.
那不用说, 这个模块的内聚性肯定低的没话讲了.
比如, 你登录模块完全不依赖外部模块, 那你数据库查询也自己写, 加密也自己写, 但这些具体实现与登录基本没有太大的关联度.
所以, 基于我的思考, 我认为: 高内聚低耦合思想的本身, 就是带有平衡性质的. 它追求在提高内聚的同时降低耦合, 达到一种最好的平衡状态.
拔高视野
高内聚低耦合并非只是软件工程的思想, 但凡是基数变大关系变复杂, 高内聚低耦合都是解决混乱的好思想.
比如, 团队管理时, 只有三个人时, 你可能甚至不需要有部门的概念.
但如果有三十个人, 你就需要将这些人按关联度 (内聚性) 放在不同的部门中, 而各个部门之间的依赖关系不能太复杂(低耦合).
怎么理解呢?
比如, 如果运营部日常工作, 需要产品部, 财务部, 销售部, 人力资源部, 行政部, 运输部...... 十几个部门的协助, 那, 任何一个部门缺席, 都会导致运营部门不能干活!!
这还得了?
所以, 在设计工作职责和机制, 人员管理上, 高内聚低耦合的思想依旧能提供一定指导.
精炼一点的话, 只要复杂度和量级能够达到 "工程" 级, 高内聚低耦合思想就都能提供指导.
说完内聚和耦合, 接下来说几个很常见的词.
依赖倒置, 控制反转, 依赖注入, 这三个词都是在面向对象设计中很常见的词语.
依赖倒置
依赖倒置是面向对象设计中能够降低耦合度的重要的设计原则.
它有两个主要思想:
高层次的模块不应该依赖于低层次的模块, 他们都应该依赖于抽象
抽象不应该依赖于具体实现, 具体实现应该依赖于抽象
怎么来理解呢?
我们需要先理解依赖这个词: 当 A 依赖于 B, 我们可以理解为, A 没有 B 就不行了, 反过来 B 没有 A 无所谓.
在面向对象的设计中, 我们会在开发具体逻辑之前, 定义一些接口.
这个接口, 就是抽象出来的东西.
接口本身没有任何能力, 不提供任何具体的技能, 他只是指导了具体功能应该长什么样子.(这就是抽象, 抽象是非细节, 非具体)
有了接口这层抽象, 不论是调用方, 还是实现方, 都依赖于接口, 而非依赖于具体, 这就是一种依赖倒置.
比如, 你出去买东西, 你得拿钱去.
但在远古时期, 大家都是以物易物的, 那时候就很麻烦, 但有了钱币这个抽象之后, 大家依赖于钱币, 不依赖于具体的物. 这使得交易变得更加灵活.
那么, 依赖倒置有什么好处呢?
试想, 仍然以接口的抽象为例.
如果我们调用方直接依赖于实现方, 那么实现方发生剧烈变动时, 调用方的调用动作也得跟着变动.
当一个项目变得复杂时, 一旦底层实现方发生一丁点变动, 都有可能对上层数量庞大的调用方产生剧烈影响.
这也是高耦合的一种现象(上下层依赖关系太复杂).
但如果, 我们本着依赖倒置的原则, 上下层都依赖于接口, 那么, 只要接口不变, 上下层双方内部无论怎么变, 都不会对对方产生影响.
所以, 有时候接口, 也会被称作契约.
因为只要你能履约, 不论你发生什么改动, 我都不管你.
依赖倒置原则, 很好的降低了耦合度.
控制反转
控制反转, 也是为了降低软件的耦合度而提出的一种面向对象设计原则.
怎么理解呢?
我们可以按照字面意义来理解, 它就是在说: 控制权被反转了.
比如, 一件事情本来由 A 来控制的, 现在变成了 B 来控制.
最形象的例子莫过于: new 一个对象.
比如:
在 A 类中, 我们需要依赖 B 类的实例, 那我们会在 A 的代码中, new 一个 B, 然后使用 B 的功能.
但我们会遇到一个问题, 如果 B 哪天不能工作了, 我们需要由 C 来替代 B.
这时候, 我们就需要在 A 的代码中修改 new B 那一段代码, 改成 new C.
如果, 我们之前有 A1,A2,A3......A100, 都依赖于 B, 我们就需要分别从它们的代码中, 把 new B 改成 new C.
这显然没有什么维护性可言.
控制反转就是为了避免这种问题的发生而提出的设计原则(设计模式).
就是让 A1 一直到 A100, 将 new 的控制权交出去.
他们只管得到一个实例, 至于这个实例, 谁来 new, 他们不管, 他们只管这个实例能用就行.
那么, 这些 A 就只负责取得实例以及使用其带来的功能, 将控制权转出去的同时, 也就不用去管 B 升级为 C 的破事了, 谁控制谁去管吧.
此时, A 就乐得清闲了.
那么, 通过什么样的方式让 A 把控制权交出去呢?
或者说, 如何实现控制反转呢?
依赖注入
依赖注入, 是实现控制反转的一种方式.
从这句描述, 我们就能知道依赖注入和控制反转之间的关系了.
这个具体的方式, 其实很简单.
上面说把控制权交出去, 那我们的 A 仍然还需要拿到一个 B 或者 C 的实例才能正常工作.
那就通过在 A 被实例化的时候, 把这个实例从外界注入到 A 中去, 常见的会在构造函数中注入.
这就是依赖注入.
其实就是通过你能想到的任何形式, 将 A 想要的实例传到 A 里面.
注入这个词, 很生动形象.
结合
那么, 我们可以结合一下依赖倒置, 控制反转, 依赖注入这三个词.
实际上, 我们从上面一句话中可以拆分出三个字各自的意义.
从这句话中, 我们能够解读出, 三者之间是互相配合的.
这句话是:
他们只管得到一个实例, 至于这个实例, 谁来 new, 他们不管, 他们只管这个实例能用就行.
我们知道这句话如果实现了, 那就是一次对控制反转原则的贯彻.
而上面 "得到" 一词, 便是依赖注入来实现的.
"能用" 一词, 则是基于依赖倒置原则实现的.
来源: http://www.jianshu.com/p/743a7a329c1e