设计模式 面向对象设计原则

遵守面向设计原则,能够提高代码的可维护性和可复用性。

一、概述

在面向对象中有许多设计原则,遵守这些原则,能够提高代码地可维护性和可复用性。设计模式都或多或少地符合面向对象设计原则。

二、单一职责原则

1. 定义

一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。

2. 说明

在软件系统中,一个类承担的职责越多,它被复用的可能性就越小,且维护的难度也更大。因此,应该尽可能地保证一个类有且仅有一个职责。单一职责原则是实现高聚合、低耦合的指导方针。

3. 示例

1
2
3
4
5
6
7
8
9
10
class Utils {

void sum(int num1, int num2) {
return num1 + num2;
}

void getConnection() {
// 连接数据库
}
}

应修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MathUtil {

void sum(int num1, int num2) {
return num1 + num2;
}

}


class DBUtil {

void getConnection() {
// 连接数据库
}

}

三、开闭原则

1. 定义

软件实体应该对扩展开放,对修改关闭。

2. 说明

如果软件符合开闭原则,则程序员可以在不修改原有代码的情况下对软件进行扩展,使得软件具有更好的可维护性。

3. 实现

通常采用抽象化设计的方式实现开闭原则,即增加一个抽象层,通过抽象层来维系整个软件的运作,而具体代码在实现类中编写。

这样,如果需要修改软件,无需对原有代码做改动,只需要增加新的实现类即可。

4. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface 接口名 {

void fun();

}


class 接口名Impl {

void fun() {
···
}

}


// 当需要扩展时,增加实现类并替换即可
class 接口名Impl2 {

void fun() {
···
}

}

四、里氏代换原则

1. 定义

所有引用基类的地方必须能透明地使用其子类的对象。

使用对象的地方,将对象替换为其子类对象,应该不影响程序的执行

2. 说明

符合里氏代换原则时,可以在程序中将任意一个对象替换为其子类的对象,程序将不会产生错误和异常。

里氏代换原则是实现开闭原则的重要方式之一,里氏代换原则使得程序员可以在不更改原有代码的基础上对程序进行扩展,只需要将对象替换为实现了新功能的子类对象即可。

3. 示例

有食堂类如下:

其中,变量 food 的类为抽象 Food 类。

1
2
3
4
5
6
7
8
9
class 食堂 {

Food food;

setFood(Food food) {
this.food = food;
}

}

在调用 setFood 方法时,可以传入任意子类的对象:

1
2
3
食堂 食堂 = new 食堂();
食堂.setFood(new 胡萝卜());
食堂.setFood(new 肉());

五、依赖倒转原则

1. 定义

高层模块不应该依赖底层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

2. 说明

面向接口编程,而不是面向实现编程。

在类中需要依赖其它类时(其它类作为类的成员属性、方法参数),应该使用接口、抽象类,而不是具体类。在编写代码时,使用抽象类进行变量;在类被实际使用时,再通过某种方法调用具体的实现类对象。

3. 示例

不符合依赖倒转原则:

People 类:

1
2
3
4
5
6
7
8
9
10
11
class People {

void eat胡萝卜(胡萝卜 胡萝卜) {
// 吃胡萝卜
}

void eat肉(肉 肉) {
// 吃肉
}

}

喂饭时:

1
2
3
People people = new People();
people.eat胡萝卜(new 胡萝卜());
people.eat肉(new 肉());

符合依赖倒转原则:

People 类:

1
2
3
4
5
6
7
class People {

void eat(Food food) {
// 吃东西
}

}

喂饭时:

1
2
3
People people = new People();
people.eat(new 胡萝卜());
people.eat(new 肉());

六、接口隔离原则

1. 定义

客户端不应该依赖那些它不需要的接口。

2. 说明

接口不应该过大,接口应该尽量细化,接口应该负责尽可能少的工作。

在面向对象的编程语言中,实现一个接口意味着实现接口中的所有方法,因此应该将使接口职责单一,避免接口的实现类实现“无用方法”。

七、合成复用原则

1. 定义

优先使用对象组合,而不是通过继承来达到复用的目的。

2. 说明

在面向对象的语言中,一般通过两种方法实现代码的复用:

  • 组合:将旧对象作为成员变量纳入到新对象之中,从而可以在新对象中调用旧对象,实现代码复用
  • 继承:将新类继承旧类,从而可以在新对象中调用父类的方法,实现代码复用

对比而言,使用继承来进行复用的问题有:

  • 由于是继承关系,旧类中的代码将会暴露,破坏了封装性
  • 一旦旧类发生改变,则新类也需要做相应改变,有一定的耦合性
  • 灵活性不够,例如新类无法通过继承同时复用多个旧类

因此,推荐使用组合达到复用的目的

八、迪米特法则

1. 定义

每一个软件单位对其它单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

2. 说明

一个软件实体应当尽可能少地与其它实体发生相互作用,简单来说,对象应该与尽可能少的其它对象进行交互。

应用迪米塔法则将会降低系统的耦合度,使类与类之间保持松散的耦合关系。

参考

  • 《Java 设计模式》