站内链接:

内聚和耦合

模块独立性: 每一个模块仅仅完成赋予的独立子功能, 并且与其他模块的联系尽可能的少并且简单. 定性的度量标准: 耦合度, 内聚度

Coupling

耦合度,描述了两个或多个模块之间的相互依赖程度,软件系统中各个模块相互联系紧密程度的一种度量, 联系越紧密, 耦合度越高, 模块的独立性越差. 耦合度的高低直接取决于:接口的复杂性, 调用的方式, 传递的信息。耦合度分类如下:

  • 无直接耦合: 没有任何关系的两个子模块
  • 数据耦合: 模块之间存在调用关系, 传递的是简单的数据值, 值传递, 并非引用传递.
  • 标记耦合: 模块之间传递的是数据结构, 使用引用传递该指针到另外一个模块, 要求引用模块了解另外一个模块关于本条数据的内部构造.
  • 控制耦合: 调用模块传递控制变量(开关, 标志等)给被调用模块, 用于有选择的执行某一项功能.
  • 公共耦合: 通过一个公共数据环境来对两个逻辑上耦合度很高的模块进行”纽带”, 从而降低模块之间的耦合度, “纽带”可以随时丢弃, 丢弃之后两个模块仍旧独立运行, 一些功能缺失.
  • 内容耦合: 最高耦合, 模块之间互相使用内部数据, 通过非正常入口调用另一个模块内部.

Cohesion

内聚性,模块的功能强度的度量, 即一个模块内部各个元素彼此结合的紧密程度的度量.若一个模块内各元素(语名之间, 程序段之间)联系的越紧密, 则它的内聚性就越高,高内聚意味着模块内的所有元素都紧密地围绕着一个单一的任务或责任。内聚分类如下:

  • 偶然内聚: 指一个模块内的各处理元素之间没有任何联系.
  • 逻辑内聚: 指模块内执行几个逻辑上相似的功能, 通过参数确定该模块完成哪一个功能.
  • 时间内聚: 把需要同时执行的动作组合在一起形成的模块为时间内聚模块.
  • 通信内聚: 指模块内所有处理元素都在同一个数据结构上操作(有时称之为信息内聚), 或者指各处理使用相同的输入数据或者产生相同的输出数据.
  • 顺序内聚: 指一个模块中各个处理元素都密切相关于同一功能且必须顺序执行, 前一功能元素输出就是下一功能元素的输入.
  • 功能内聚: 这是最强的内聚, 指模块内所有元素共同完成一个功能, 缺一不可.与其他模块的耦合是最弱

案例分析

准备充分的数据: 在开发 django API 的时候, 尽可能保证各个 APP 独立性, 如果有不同 app 之间的数据沟通, 都尽可能的将所有准备数据放到 api/view 层进行处理, 确保每一次进入 service 都是准备好的完全数据值.

servcie: service 内部尽可能的高内聚, 多个 service 共同实现一个尽可能内聚的功能, 如果不行, 那说明 app 还需要进一步拆分.

纽带: 使用纽带 APP 或者 API 来连接多个 APP.

面向对象

多态

  1. 封装:类似命名空间的概念,将对象的状态(属性)和行为(方法)包装在一起,对外界隐藏对象的内部实现细节,边界是编程实践或者工程实践非常重要的东西。
  2. 继承

允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。通过继承,子类自动获得父类的所有特性,而无需重新编写相同的代码。从而达到代码复用、类层次结构、定制和扩展的功能。注意,继承在有些语言中还分单继承和多继承。

根据里氏替换原则,继承也不能滥用,过度使用继承可能会导致代码结构复杂和难以维护,很多使用是建议使用合成复用原则、接口原则而非继承机制来实现多态效果的。

  1. 多态

Polymorphism 能够使用相同的接口来操作不同类型的对象。多态性允许不同的类的对象响应相同的消息(或方法调用),但每个类的对象可以根据该类的方法具体实现以不同的方式响应。多态分为编译时多态、运行时多态,下面是两者的使用实例和说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// a. 方法重载多态,在同一个类中定义多个名称相同但参数列表不同
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
}

// 使用
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3)); // 调用第一个add方法
System.out.println(calc.add(5.5, 3.5)); // 调用第二个add方法

// b. 方法重写多态
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}

class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Bark");
}
}

class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}

// 使用
Animal myAnimal = new Dog();
myAnimal.makeSound(); // 输出:Bark,运行时多态

myAnimal = new Cat();
myAnimal.makeSound(); // 输出:Meow,运行时多态

通过使用多态,提高了代码的灵活度、提高了代码的扩展性、提高了代码的复用性,当然也同时提高了代码复杂度,这之间的平衡类似耦合和内聚,需要自己把握一个度。

抽象和接口

  1. 抽象

abstraction,一种将复杂性隐藏,仅展示必要特征的方法,在面向对象中一般通过抽象类来实现这种效果,定义一种不能被实例化的类,只能作为其他类的基类。抽象类可以包含抽象方法和具体方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Animal {
abstract void video();

public void eat() {
System.out.println("This animal eats food.");
}
}

class Dog extends Animal {
@Override
void video() {
System.out.println("Bark");
}
}
  1. 接口

interface,一组方法规范,没有实现的内容,这是一个完全抽象的结构,没有任何具体方法的实现,主要是对象之间的一组约定或协议

1
2
3
4
5
6
7
8
9
10
interface Animal {
void makeSound();
}

class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Miao~");
}
}

下面的抽象和接口的一些区别

  • 方法实现:抽象类可以包含抽象方法和具体方法(共同模板代码),接口在大部分语境下都只包含抽象方法
  • 使用场景:抽象类在当多个类有共同的方法和属性,且你想在某个层次上共享代码时使用;接口则是指定一组类都必须实现的方法

七大设计原则

  • 单一职责原则
  • 接口隔离原则
  • 依赖倒置原则
  • 合成复用原则
  • 里氏替换原则
  • 开放封闭原则
  • 迪米特法则