(29) – 高内聚低耦合
最后更新于:2022-04-01 07:29:46
## 连载:面向对象葵花宝典:思想、技巧与实践(29) - 高内聚低耦合
**高内聚低耦合**,可以说是每个程序猿,甚至是编过程序,或者仅仅只是在大学里面学过计算机,都知道的一个简单的设计原则。
虽然如此流行和人所众知,但其实**真正理解的人并不多,很多时候都是人云亦云**。
===============================================================
要想真正理解“高内聚低耦合”,需要回答两个问题:
1)为什么要高内聚低耦合?
2)高内聚低耦合是否意味内聚越高越好,耦合越低越好?
**第一个问题:为什么要高内聚低耦合?**
经典的回答是:降低复杂性。
确实很经典,当然,其实也是废话!我相信大部分人看了后还是不懂,什么叫复杂性呢?
要回答这个问题,其实可以采用逆向思维,即:如果我们不做到这点,将会怎样?
首先来看内聚,试想一下,假如我们是低内聚,情况将会如何?
前面我们在阐述内聚的时候提到内聚的关键在于“元素的凝聚力”,如果内聚性低,则说明凝聚力低;对于一个团队来说,如果凝聚力低,则一个明显的问题是“不稳定”;对于一个模块来说,内聚性低的问题也是一样的“不稳定”。具体来说就是如果一个模块内聚性较低,则这个模块很容易变化。一旦变化,设计、编码、测试、编译、部署的工作量就上来了,而一旦一个模块变化,与之相关的模块都需要跟着改变。
举一个简单的例子,假设有这样一个设计不好的类:Person,其同时具有“学生”、“运动员”、“演员”3个职责,有另外3个类“老师”、“教练”、“导演”依赖这个类。
**Person.java**
~~~
package com.oo.cohesion.low;
/**
* “人”的类设计
*
*/
public class Person {
/**
* 学生的职责:学习
*/
public void study() {
//TODO: student's responsibility
}
/**
* 运动员的职责:运动
*/
public void play(){
//TODO: sportsman's responsibility
}
/**
* 演员的职责:扮演
*/
public void act(){
//TODO: actor's responsibity
}
}
~~~
**Teacher.java**
~~~
package com.oo.cohesion.low;
/**
* “老师”的类设计
*
*/
public class Teacher {
public void teach(Person student){
student.study(); //依赖Person类的“学生”相关的职责
}
}
~~~
**Coach.java**
~~~
package com.oo.cohesion.low;
/**
* “教练”的类设计
*
*/
public class Coach {
public void train(Person trainee){
trainee.play(); //依赖Person类的“运动员”职责
}
}
~~~
**Director.java**
~~~
package com.oo.cohesion.low;
/**
* “导演”的类设计
*
*/
public class Director {
public void direct(Person actor){
actor.act(); //依赖Person类“演员”的相关职责
}
}
~~~
在上面的样例中,Person类就是一个典型的“低内聚”的类,很容易发生改变。比如说,现在老师要求学生也要考试,则Person类需要新增一个方法:test,如下:
~~~
package com.oo.cohesion.low;
/**
* “人”的类设计
*
*/
public class Person {
/**
* 学生的职责:学习
*/
public void study() {
//TODO: student's responsibility
}
/**
* 学生的职责:考试
*/
public void test(){
//TODO: student's responsibility
}
/**
* 运动员的职责:运动
*/
public void play(){
//TODO: sportsman's responsibility
}
/**
* 演员的职责:扮演
*/
public void act(){
//TODO: actor's responsibity
}
}
~~~
由于Coach和Director类都依赖于Person类,Person类改变后,虽然这个改动和Coach、Director都没有关系,但Coach和Director类都需要重新编译测试部署(即使是PHP这样的脚本语言,至少也要测试)。
同样,Coach和Director也都可能增加其它对Person的要求,这样Person类就需要同时兼顾3个类的业务要求,且任何一个变化,Teacher、Coach、Director都需要重新编译测试部署。
对于耦合,我们采用同样的方式进行分析,**即:如果高耦合,将会怎样?**
高耦合的情况下,模块依赖了大量的其它模块,这样任何一个其它依赖的模块变化,模块本身都需要受到影响。所以,高耦合的问题其实也是“不稳定”,当然,这个不稳定和低内聚不完全一样。对于高耦合的模块,可能本身并不需要修改,但每次其它模块修改,当前模块都要编译、测试、部署,工作量同样不小。
我们同样以一个样例来说明。假设我们要设计一个Boss类,Boss是要管整个公司的,那么我们假设这是一家“麻雀虽小五脏俱全”的公司,同时有“研发、测试、技术支持、销售、会计、行政”等部门。
**Boss.java**
~~~
package com.oo.coupling.high;
/**
* “老板”类
*
*/
public class Boss {
private Tester tester;
private Developer developer;
private Supporter supporter;
private Administration admin;
private Accountant accountant;
/**
* Boss每天检查工作,看到下面的代码,是否觉得Boss很忙?
*/
public void check(){
//检查测试工作
tester.report();
//检查研发工作
developer.report();
//检查技术支持工作
supporter.report();
//检查行政工作
admin.report();
//检查财务工作
accountant.report();
}
}
~~~
**Accountant.java**
~~~
package com.oo.coupling.high;
/**
* “财务”类
*
*/
public class Accountant {
public void report(){
System.out.print("Accountant report");
}
}
~~~
**Administration.java**
~~~
package com.oo.coupling.high;
/**
* “行政”类
*
*/
public class Administration {
public void report(){
System.out.print("Administration report");
}
}
~~~
**Developer.java**
~~~
package com.oo.coupling.high;
/**
* “开发”类
* @author Administrator
*
*/
public class Developer {
public void report(){
System.out.print("Developer report");
}
}
~~~
**Supporter.java**
~~~
package com.oo.coupling.high;
/**
* “技术支持”类
*
*/
public class Supporter {
public void report(){
System.out.print("Supporter report");
}
}
~~~
**Tester.java**
~~~
package com.oo.coupling.high;
/**
* “测试”类
*
*/
public class Tester {
public void report(){
System.out.print("Tester report");
}
}
~~~
好吧,Boss很忙,我们也很钦佩,但是有一天,研发的同学觉得他们应该做更多的事情,于是他们增加了一个“研究”的工作,且这个工作也不需要报告给Boss,例如:
~~~
package com.oo.coupling.high;
/**
* “开发”类
* @author Administrator
*
*/
public class Developer {
public void report(){
System.out.print("Developer report");
}
/**
* 研发新增加一个研究的任务,这个任务也不需要向Boss汇报
*/
public void research(){
System.out.print("Developer is researching big data, hadoop :)");
}
}
~~~
虽然这个工作不需要报告给Boss,但由于Developer类修改了,那么Boss类就需要重新编译测试部署。其它几个Tester、Supporter类等也是类似,一旦改变,即使这些类和Boss类半毛钱关系都没有,Boss类还是需要重新编译测试部署,所以Boss类是很不稳定的。
所以,无论是“低内聚”,还是“高耦合”,其本质都是“不稳定”,不稳定就会带来工作量,带来风险,这当然不是我们希望看到的,所以我们应该做到“高内聚低耦合”。
回答完第一个问题后,我们来看第二个问题:高内聚低耦合是否意味着内聚越高越好,耦合越低越好?
按照我们前面的解释,内聚越高,一个类越稳定;耦合越低,一个类也很稳定,所以当然是内聚越高越好,耦合越低越好了。
但其实稍有经验的同学都会知道这个结论是错误的,并不是内聚越高越好,耦合越低越好,**真正好的设计是在高内聚和低耦合间进行平衡,也就是说高内聚和低耦合是冲突的**。
我们详细来分析一下为什么高内聚和低耦合是冲突的。
对于内聚来说,最强的内聚莫过于一个类只写一个函数,这样内聚性绝对是最高的。但这会带来一个明显的问题:类的数量急剧增多,这样就导致了其它类的耦合特别多,于是整个设计就变成了“高内聚高耦合”了。由于高耦合,整个系统变动同样非常频繁。
同理,对于耦合来说,最弱的耦合是一个类将所有的函数都包含了,这样类完全不依赖其它类,耦合性是最低的。但这样会带来一个明显的问题:内聚性很低,于是整个设计就变成了“低耦合低内聚”了。由于低内聚,整个类的变动同样非常频繁。
对于“低耦合低内聚”来说,还有另外一个明显的问题:几乎无法被其它类重用。原因很简单,类本身太庞大了,要么实现很复杂,要么数据很大,其它类无法明确该如何重用这个类。
所以,内聚和耦合的两个属性,排列组合一下,**只有“高内聚低耦合”才是最优的设计**。
因此,在实践中我们需要牢牢记住需要在高内聚和低耦合间进行平衡,而不能走极端。 具体如何平衡,且听下回分解。