Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

gitlab-ci

发表于 2016-09-08

1. 背景

团队开发项目遇到的问题:

  • 版本更新较为频繁的问题。
  • 测试覆盖不到位的问题。

2. Jenkins和Gitlab CI的简单比较

Jenkins是老牌持续集成开源平台:

  • 优点:现在已经发展到2.0,各种功能插件非常丰富,支持groovy编写脚本,尤其支持LDAP等与企业集成的功能,基本上是CI的不二之选。
  • 缺点:界面丑陋,配置相对复杂,一般需要专门的IT运维人员维护,与代码管理平台结合度相对较低,对Docker支持需要安装额外的插件。

Gitlab CI,新锐CI平台:

  • 优点:界面美观,原生支持docker及多种机制,与代码管理工具结合度非常高,配置简单,使用YAML语言,程序员即可编写自动编译脚本。
  • 缺点:控制性不够好,生态还不够齐全,YAML有一定的局限性,复杂任务需要些shell完成。

Jenkins适合复杂集成任务,而Gitlab CI适合相对简单的持续集成。

企业中一般会2个同时使用,而两者的分工可以为:

  • develop环境,直接使用Gitlab CI做持续的编译、测试、开发环境部署、API测试、客户端打包等工作
  • staging和production环境,使用Gitlab CI+Jenkins辅助配合的方式,或者直接由jenkins全包,因为上线会涉及到不同版本的资源更改、根据参数调整编译和打包等相对复杂的任务,这时Gitlab CI就会相形见肘了,虽然可以自己写Shell来支持,但控制性(如鸡肋的trigger)差强人意

3. 项目中的使用

项目中会使用J2EE, 大致阶段分为:代码检视、编译、测试、打包、部署。

dev环境,均使用Gitlab CI,比较简单,可以直接查询Gitlab CI文档,如果用过Travis CI,对于Gitlab CI就更不会有问题了。

staging和production环境,选用Gitlab CI+Jenkins混合的模式,具体阶段分工为:

  • Gitlab CI负责:代码检视、编译、打包、测试
  • Jenkins负责:打包(主要是资源替换)、部署

在gitlab中完成持续集成CI包括两个操作:

  • 配置一个Runner(用来编译、测试、打包的服务器节点)。
  • 在项目根目录增加YAML格式的CI脚本文件.gitlab-ci.yml。

3.1 install configue gitlab-ci-multi-runner

  • GitLab 部署 CI 的第一步就是安装 gitlab-ci-multi-runner,你可以把它理解为:跑 CI 的服务。

    将一台mac计算机配置成我们的一个Runner,基本原理就是在Mac上安装一个代理程序gitlab-ci-multi-runner,然后将mac注册到gitlab服务器端,然后这台mac机器就能接收到gitlab服务器下发的CI任务,完成相应的编译、测试、打包等工作,然后将结果反馈给gitlab服务器。

在一台Mac机器上执行如下命令安装gitlab-ci-multi-runner:

1
2
3
sudo curl --output /usr/local/bin/gitlab-ci-multi-runner https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-ci-multi-runner-darwin-amd64
sudo chmod +x /usr/local/bin/gitlab-ci-multi-runner
  • 进入项目的Runner配置页面,如下图所示:

gitlab_config_runner

  • 在Mac机器上执行如下命令,将这台Mac注册到gitlab并绑定到我们示例项目。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gitlab-ci-multi-runner register
#Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):
##输入上图中的URL.
#Please enter the gitlab-ci token for this runner:
##输入上图中的token.
#Please enter the gitlab-ci description for this runner:
##输入一个描述信息,这里我们输入mac_runner
#Please enter the gitlab-ci tags for this runner (comma separated):
##输入一些标签,这里我们输入"mac,xcode7.1"
# Registering runner... succeeded runner=euasz2j9
#Please enter the executor: docker-ssh+machine, docker, docker-ssh, parallels, shell, ssh, virtualbox, docker+machine:
##这里我们输入shell,因为ios项目的编译、测试、打包我们都采用脚本来执行。
#Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
#注册成功,接下来启动它
gitlab-ci-multi-runner install
gitlab-ci-multi-runner start

现在我们的Mac机器就注册为一个Runner了,查看项目的Runner页面,如下图所示:

gitlab_runner_running

我们的Runner就成功注册上了。接下来就可以编写CI的脚本了。

  • 增加CI脚本文件.gitlab-ci.yml

在项目根目录创建.gitlab-ci.yml文件,内容如下:

1
2
3
4
5
6
7
8
9
before_script:
- export JAVA_HOME=/usr/local/jdk1.7.0_79
job:
only:
- master(每一次提交到master分支触发script)
script:
- mvn clean package -Ptest -q -Dmaven.javadoc.skip=true

before_script 部分将在每一个job前被执行。每个job包含的参数,例如script (shell script)、tags (只有运行这个tag/tags才允许选择这个构建),以及only 或except 参数来定义允许运行构建的分支名称。only 部分优先于 “except”。参见:Configuration of your builds with .gitlab-ci.yml

参考**

  • 持续集成是什么?

开闭原则

发表于 2016-09-06

开闭原则是设计模式六大原则中最重要也是最“虚”的一个原则。为什么说它最重要呢?因为前面五大原则的目的都是为了实现这个开闭原则,开闭原则相当于它们的主旨思想。为什么说它“虚”呢?因为开闭原则只是个指导思想,不像另外五大原则都有具体可行的指导方法。

1、什么是开闭原则

Software entities like classes, modules and functions shoule be open for extension but closed for modifications.(一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。)

开闭原则的定义很短,就是对扩展开放,对修改关闭。但是为什么要遵守这一个原则呢?

做过实际项目的筒子们应该都会深有体会,一个软件在其生命周期内都会发生很多变化,这几乎是不可避免的。无论是需求的变化、业务逻辑的变化、程序代码的变化等等,这些变化都有可能对整个软件的稳定性造成一定的威胁。

而开闭原则就是应对这些变化的,它告诉我们应该通过扩展来实现变化,而不是通过修改已有的代码。

2、例子

UML类图:

img

程序代码如下:

  1. 学生类,每个学生都有姓名和成绩:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Student {
private String name;
private String grade;
public Student(String name, String grade) {
this.name = name;
this.grade = grade;
}
public String getName() {
return name;
}
public String getGrade() {
return grade;
}
}
  1. 老师类,每个老师管理一群的学生:
1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.ArrayList;
import java.util.List;
public class Teacher {
public final static List<Student> students = new ArrayList<Student>();
static {
students.add(new Student("张三", "60"));
students.add(new Student("李四", "70"));
students.add(new Student("王五", "80"));
}
}
  1. 场景类:
1
2
3
4
5
6
7
8
public class Client {
public static void main(String[] args) {
Teacher teacher = new Teacher();
for (Student student : teacher.students) {
System.out.println("姓名:" + student.getName() + " 成绩:" + student.getGrade());
}
}
}

运行结果如下:

1
2
3
姓名:张三 成绩:60
姓名:李四 成绩:70
姓名:王五 成绩:80

更改需求

把同学们的成绩按照级别来分,分别有优秀,良好,一般,及格,不及格这几种。

直接修改Student类的getGrade方法不就行了嘛? 可能有很多人在实际项目中都是这么做的,但是这就违背了开闭原则,开闭原则要求我们尽量不要修改已有的代码,尽量通过扩展来实现改变。

可以通过扩展已有的代码来实现改变,可以增加一个LevelStudent来继承Student,并扩展修改getGrade方法。

修改后的UML类图如下所示:

img

增加LevelStudent类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class LevelStudent extends Student {
public LevelStudent(String name, String grade) {
super(name, grade);
}
@Override
public String getGrade() {
String level = null;
int grade_ = Integer.valueOf(super.getGrade());
if (grade_ >= 90) {
level = "优秀";
} else if (grade_ >= 80 && grade_ < 90) {
level = "良好";
} else if (grade_ >= 70 && grade_ < 80) {
level = "一般";
} else if (grade_ >= 60 && grade_ < 70) {
level = "及格";
} else if (grade_ < 60) {
level = "不及格";
}
return level;
}
}

3、总结

开闭原则是对扩展开放,对修改关闭。

开闭原则的主旨是为了拥抱变化。

在六大原则中,开闭原则只是一个思想,没有具体实际操作方法。其他五大原则都是为了实现这个开闭思想的一些方法和工具。

想要遵守开闭原则,就需要一个设计合理的系统。可以说在做系统设计的时候就要考虑到未来的扩展和改变。

迪米特法则

发表于 2016-09-06

迪米特法则有很多种说法,比如:一个类应该应该对其他类尽可能了解得最少;类只与直接的朋友通信等等。但是其最终目的只有一个,就是让类间解耦。

1、什么是迪米特法则

迪米特法则:Law Of Demeter,LoD。

也被称为最少知识原则,Least Knowledge Principle,LKP。

就是说一个对象应该对其他对象保持最少的了解。正如最少知识原则这个定义一样,一个类应该对其耦合的其他类或所调用的类知道得最少。所耦合的类内部无论如何复杂,怎么实现的我都不需要知道,我只调用你public出来的这些方法,其他都不用知道。

另外可以解释一下开头提到的只与直接的朋友通信,什么叫直接的朋友呢?

2、例子

光看定义可能无法完全理解它所表达的含义,以及在什么场景下才需要使用这个迪米特法则。现在就来举个例子。

现在市面上各种人脉书上很多都会提到“六度人脉”这个理论,这个理论说的是你与世界上任何一个人中间只隔了六个人。也就是说你想找任何一个人,无论这个人是政界要人,还是商界巨鳄,抑或是明星名人,你最多只通过六个人就可以联系到他。

我们暂且不论这个理论是对是错,在现实生活中我们也经常遇到这样的情况。比如你想办一件事情,但是凭借你的能力是做不到的,而你周围的朋友也无法帮你办到。但是恰好你有一个朋友认识有另外一个朋友可以办得成此事,那么你只有拜托你这位朋友中间牵线搭桥,让他的朋友帮你办好此事。

在这个例子中,我们就暂且定义你为A,你的朋友为B,你朋友的朋友为C好了。

  • 反面教材

我们先来看看表达此种关系的UML类图:

img

实现代码如下:

  1. 类A和类B是好朋友,能找到类B来帮忙:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
C c = b.getC("王五");
c.work();
}
}
  1. 类B和类C是好朋友,能知道类C来帮忙:
1
2
3
4
5
6
7
8
9
10
11
12
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
}
  1. 类C能够办成此事:
1
2
3
4
5
6
7
8
9
10
11
12
public class C {
public String name;
public C(String name) {
this.name = name;
}
public void work() {
System.out.println(name + "把这件事做好了");
}
}
  1. 场景类
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
A a = new A("张三");
a.work();
}
}

运行结果如下:

1
王五把这件事做好了

上面的输出虽然是把事情成功办好了,但是仔细看业务逻辑明显是不对的。A和C又不是好朋友,为什么在类A中会出现类C呢?他们又互相不认识。

看到这里很多人都会明白,这种场景在实际开发中是非常常见的一种情况。对象A需要调用对象B的方法,对象B有需要调用对象C的方法……就是常见的getXXX().getXXX().getXXX()……类似于这种代码。如果你发现你的代码中也有这样的代码,那就考虑下是不是违反迪米特法则,是不是要重构一下了。

  • 正确例子

为了符合迪米特法则,也为了让业务逻辑能够说得通,我们把上面的例子稍微修改一下。

UML类图如下:

img

代码如下:

  1. 类A和类B是好朋友,能找到类B来帮忙:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A {
public String name;
public A(String name) {
this.name = name;
}
public B getB(String name) {
return new B(name);
}
public void work() {
B b = getB("李四");
b.work();
}
}
  1. 类B和类C是好朋友,能知道类C来帮忙:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class B {
private String name;
public B(String name) {
this.name = name;
}
public C getC(String name) {
return new C(name);
}
public void work(){
C c = getC("王五");
c.work();
}
}
  1. 类C能够办成此事:
1
2
3
4
5
6
7
8
9
10
11
12
public class C {
public String name;
public C(String name) {
this.name = name;
}
public void work() {
System.out.println(name + "把这件事做好了");
}
}
  1. 场景类
1
2
3
4
5
6
public class Client {
public static void main(String[] args) {
A a = new A("张三");
a.work();
}
}

运行结果如下:

1
王五把这件事做好了

上面代码只是修改了下类A和B的work方法,使之符合了迪米特法则:

  • 类A只与最直接的朋友类B通信,不与类C通信;
  • 类A只调用类B提供的方法即可,不用关心类B内部是如何实现的(至于B是怎么调用的C,这些A都不用关心)。

3、总结

迪米特法则的目的是让类之间解耦,降低耦合度。只有这样,类的可复用性才能提高。

但是迪米特法则也有弊端,它会产生大量的中转类或跳转类,导致系统的复杂度提高。

所以我们不要太死板的遵守这个迪米特法则,在系统设计的时候,在弱耦合和结构清晰之间反复权衡。尽量保证系统结构清晰,又能做到低耦合。

接口隔离原则

发表于 2016-09-06

1、问题由来

  类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类B和类D来说不是最小接口,则类B和类D必须去实现他们不需要的方法。

2、什么是接口隔离原则

  接口隔离原则比较简单,有两种定义:

  • Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该强行依赖它不需要的接口)
  • The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)

  其实上述两种定义说的是同一种意思。客户端不应该依赖它不需要的接口,意思就是说客户端只要依赖它需要的接口,它需要什么接口,就提供什么接口,不提供多余的接口。“类间的依赖关系应该建立在最小的接口上”也表达这一层意思。通俗的讲就是:接口中的方法应该尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑方法。

  通过简单的代码还原开篇的问题,代码如下:

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public interface I {
public void method1();
public void method2();
public void method3();
}
public class B implements I{
@Override
public void method1() {
System.out.println("类B实现了接口I的方法1");
}
@Override
public void method2() {
System.out.println("类B实现了接口I的方法2");
}
@Override
public void method3() {//类B并不需要接口I的方法3功能,但是由于实现接口I,所以不得不实现方法3
//在这里写一个空方法
}
}
public class D implements I{
@Override
public void method2() {
System.out.println("类D实现了接口I的方法2");
}
@Override
public void method3() {
System.out.println("类D实现了接口I的方法3");
}
@Override
public void method1() {//类D并不需要接口I的方法1功能,但是由于实现接口I,所以不得不实现方法1
//在这里写一个空方法
}
}
//类A通过接口I依赖类B
public class A {
public void depend1(I i){
i.method1();
}
}
//类C通过接口I依赖类D
public class C {
public void depend1(I i){
i.method3();
}
}
public class Client {
public static void main(String[] args) {
A a = new A();
I i1 = new B();
a.depend1(i1);
C c = new C();
I i2 = new D();
c.depend1(i2);
}
}

 运行结果:

1
2
类B实现了接口I的方法1
类D实现了接口I的方法3

  从以上代码可以看出,如果接口过于臃肿,不同业务逻辑的抽象方法都放在一个接口内,则会造成它的实现类必须实现自己并不需要的方法,这种设计方式显然是不妥当的。所以我们要修改上述设计方法,把接口I拆分成3个接口,使得实现类只需要实现自己需要的接口即可。只贴出修改后的接口和实现类的代码,修改代码如下:

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
public interface I1 {
public void method1();
}
public interface I2 {
public void method2();
}
public interface I3 {
public void method3();
}
public class B implements I1,I2{
@Override
public void method1() {
System.out.println("类B实现了接口I的方法1");
}
@Override
public void method2() {
System.out.println("类B实现了接口I的方法2");
}
}
public class D implements I2,I3{
@Override
public void method2() {
System.out.println("类D实现了接口I的方法2");
}
@Override
public void method3() {
System.out.println("类D实现了接口I的方法3");
}
}

3、与单一职责原则的区别

  到了这里,有些人可能觉得接口隔离原则与单一职责原则很相似,其实不然。

  • 第一,单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
  • 第二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。

4、注意事项

  原则是前人经验的总结,在软件设计中具有一定的指导作用,但是不能完全照搬这些原则。对于接口隔离原则来说,接口尽量小,但是也要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。

依赖倒置原则

发表于 2016-09-06

1、问题由来

  类A直接依赖于类B,假如要将类A修改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑。类B和C是底层模块,负责基本的原子操作。假如修改类A,将会给程序带来不必要的风险。而遵循依赖倒置原则的程序设计可以解决这一问题。

2、什么是依赖倒置原则

  英文缩写DIP(Dependence Inversion Principle)。

  原始定义:High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

  翻译过来就三层含义:

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象。

  抽象:即抽象类或接口,两者是不能够实例化的。

  细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化。

  现在我们来通过实例还原开篇问题的场景,以便更好的来理解。下面代码描述了一个简单的场景,Jim作为人有吃的方法,苹果有取得自己名字的方法,然后实现Jim去吃苹果。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//具体Jim人类
public class Jim {
public void eat(Apple apple){
System.out.println("Jim eat " + apple.getName());
}
}
//具体苹果类
public class Apple {
public String getName(){
return "apple";
}
}
public class Client {
public static void main(String[] args) {
Jim jim = new Jim();
Apple apple = new Apple();
jim.eat(apple);
}
}

运行结果:

1
Jim eat apple

  上面代码看起来比较简单,但其实是一个非常脆弱的设计。现在Jim可以吃苹果了,但是不能只吃苹果而不吃别的水果啊,这样下去肯定会造成营养失衡。现在想让Jim吃香蕉了(好像香蕉里含钾元素比较多,吃点比较有益),突然发现Jim是吃不了香蕉的,那怎么办呢?看来只有修改代码了啊,由于上面代码中Jim类依赖于Apple类,所以导致不得不去改动Jim类里面的代码。那如果下次Jim又要吃别的水果了呢?继续修改代码?这种处理方式显然是不可取的,频繁修改会带来很大的系统风险,改着改着可能就发现Jim不会吃水果了。

  上面的代码之所以会出现上述难堪的问题,就是因为Jim类依赖于Apple类,两者是紧耦合的关系,其导致的结果就是系统的可维护性大大降低。要增加香蕉类却要去修改Jim类代码,这是不可忍受的,你改你的代码为什么要动我的啊,显然Jim不乐意了。我们常说要设计一个健壮稳定的系统,而这里只是增加了一个香蕉类,就要去修改Jim类,健壮和稳定还从何谈起。

  而根据依赖倒置原则,我们可以对上述代码做些修改,提取抽象的部分。首先我们提取出两个接口:People和Fruit,都提供各自必需的抽象方法,这样以后无论是增加Jim人类,还是增加Apple、Banana等各种水果,都只需要增加自己的实现类就可以了。由于遵循依赖倒置原则,只依赖于抽象,而不依赖于细节,所以增加类无需修改其他类。

代码如下:

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
//人接口
public interface People {
public void eat(Fruit fruit);//人都有吃的方法,不然都饿死了
}
//水果接口
public interface Fruit {
public String getName();//水果都是有名字的
}
//具体Jim人类
public class Jim implements People{
public void eat(Fruit fruit){
System.out.println("Jim eat " + fruit.getName());
}
}
//具体苹果类
public class Apple implements Fruit{
public String getName(){
return "apple";
}
}
//具体香蕉类
public class Banana implements Fruit{
public String getName(){
return "banana";
}
}
public class Client {
public static void main(String[] args) {
People jim = new Jim();
Fruit apple = new Apple();
Fruit Banana = new Banana();//这里符合了里氏替换原则
jim.eat(apple);
jim.eat(Banana);
}
}

运行结果:

1
2
Jim eat apple
Jim eat banana
  • People类是复杂的业务逻辑,属于高层模块,而Fruit是原子模块,属于低层模块。People依赖于抽象的Fruit接口,这就做到了:高层模块不应该依赖低层模块,两者都应该依赖于抽象(抽象类或接口)。
  • People和Fruit接口与各自的实现类没有关系,增加实现类不会影响接口,这就做到了:抽象(抽象类或接口)不应该依赖于细节(具体实现类)。
  • Jim、Apple、Banana实现类都要去实现各自的接口所定义的抽象方法,所以是依赖于接口的。这就做到了:细节(具体实现类)应该依赖抽象。

3、什么是倒置

  到了这里,我们对依赖倒置原则的“依赖”就很好理解了,但是什么是“倒置”呢。是这样子的,刚开始按照正常人的一般思维方式,我想吃香蕉就是吃香蕉,想吃苹果就吃苹果,编程也是这样,都是按照面向实现的思维方式来设计。而现在要倒置思维,提取公共的抽象,面向接口(抽象类)编程。不再依赖于具体实现了,而是依赖于接口或抽象类,这就是依赖的思维方式“倒置”了。

4、依赖的三种实现方式

  对象的依赖关系有三种方式来传递:

  • 接口方法中声明依赖对象。就是我们上面代码所展示的那样。
  • 构造方法传递依赖对象。在构造函数中的需要传递的参数是抽象类或接口的方式实现。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
//具体Jim人类
public class Jim implements People{
private Fruit fruit;
public Jim(Fruit fruit){//构造方法传递依赖对象
this.fruit = fruit;
}
public void eat(Fruit fruit){
System.out.println("Jim eat " + this.fruit.getName());
}
}
  • Setter方法传递依赖对象。在我们设置的setXXX方法中的参数为抽象类或接口,来实现传递依赖对象。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
//具体Jim人类
public class Jim implements People{
private Fruit fruit;
public void setFruit(Fruit fruit){//setter方式传递依赖对象
this.fruit = fruit;
}
public void eat(){
System.out.println("Jim eat " + this.fruit.getName());
}
}

5、优点

  从上面的代码修改过程中,我们可以看到由于类之间松耦合的设计,面向接口编程依赖抽象而不依赖细节,所以在修改某个类的代码时,不会牵涉到其他类的修改,显著降低系统风险,提高系统健壮性。

  还有一个优点是,在我们实际项目开发中,都是多人团队协作,每人负责某一模块。比如一个人负责开发People模块,一人负责开发Fruit模块,如果未采用依赖倒置原则,没有提取抽象,那么开发People模块的人必须等Fruit模块开发完成后自己才能开发,否则编译都无法通过,这就是单线程的开发。为了能够两人并行开发,设计时遵循依赖倒置原则,提取抽象,就可以大大提高开发进度。

6、总结

  说到底,依赖倒置原则的核心就是面向接口编程的思想,尽量对每个实现类都提取抽象和公共接口形成接口或抽象类,依赖于抽象而不要依赖于具体实现。依赖倒置原则的本质其实就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。但是这个原则也是6个设计原则中最难以实现的了,如果没有实现这个原则,那么也就意味着开闭原则(对扩展开放,对修改关闭)也无法实现。

1…212223…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces