思考并回答以下问题:
到底面向?编程
面向过程编程(ProcedureOriented、简称PO)和面向对象编程(ObjectOriented、简称OO)我们一定听过,然而实际企业级开发里受用更多的一种编程思想那就是:面向接口编程(Interface-Oriented)!
接口这个概念我们一定不陌生,实际生活中最常见的例子就是:插座!
我们只需要事先定义好插座的接口标准,各大插座厂商只要按这个接口标准生产,管你什么牌子、内部什么电路结构,这些均和用户无关,用户拿来就可以用;而且即使插座坏了,只要换一个符合接口标准的新插座,一切照样工作!
同理,实际代码设计也是这样!
我们在设计一个软件的代码架构时,我们都希望事先约定好各个功能的接口(即:约定好接口签名和方法),实际开发时我们只需要实现这个接口就能完成具体的功能!后续即使项目变化、功能升级,程序员只需要按照接口约定重新实现一下,就可以达到系统升级和扩展的目的!
正好,Java中天生就有interface这个语法,这简直是为面向接口编程而生的!
所以接下来落实到代码上,举个通俗一点的小例子唠一唠,实际业务代码虽然比这个复杂,但原理是一模一样的。
例子
假如哪一天程序羊真发达了,一口豪气买了两辆豪车,一辆五菱宏光、一辆飞度、并且还专门聘请了一位驾驶员来帮助驾驶。
两辆豪车在此:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Wuling
{
public void drive()
{
System.out.println("驾驶五菱宏光汽车");
}
}
public class Fit
{
public void drive()
{
System.out.println("驾驶飞度汽车");
}
}
驾驶员定义在此:
驾驶员定义了两个drive()方法,分别用来驾驶两辆车: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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143public
class
Driver
{
public
void
drive
(
Wuling
wuling
)
{
wuling
.
drive
();
// 驾驶五菱宏光的方法
}
public
void
drive
(
Fit
fit
)
{
fit
.
drive
();
// 驾驶飞度的方法
}
// 用于测试功能的 main()函数
public
static
void
main
(
String
[]
args
)
{
// 实例化两辆新车
Wuling
wuling
=
new
Wuling
();
Fit
fit
=
new
Fit
();
// 实例化驾驶员
Driver
driver
=
new
Driver
();
driver
.
drive
(
wuling
);
// 帮我开五菱宏光
driver
.
drive
(
fit
);
// 帮我开飞度
}
}
这暂且看起来没问题!日子过得很融洽。
但后来过了段时间,程序羊又变得发达了一点,这次他又豪气地买了一辆新款奥拓(Alto)!
可是现有的驾驶员类Driver的两个drive()方法里都开不了这辆新买的奥拓该怎么办呢?
代码的灵活解耦
这时候,我想应该没有谁会专门再去往 Driver类中添加一个新的 drive()方法来达到目的吧?毕竟谁也不知道以后他还会不会买新车!
这时候如果我希望我聘请的这位驾驶员对于所有车型都能驾驭,该怎么办呢?
很容易想到,我们应该做一层抽象。毕竟不管是奥拓还是奥迪,它们都是汽车,因此我们定义一个父类叫做汽车类 Car,里面只声明一个通用的 drive()方法,具体怎么开先不用管:
// 抽象的汽车类Car,代表所有汽车
public
class
Car
{
void
drive
()
{
}
// 通用的汽车驾驶方法
}
这时,只要我新买的奥拓符合 Car定义的驾驶标准即可被我的驾驶员驾驶,所以只需要新的奥拓来继承一下 Car类即可:
public
class
Alto
extends
Car
{
public
void
drive
()
{
System
.
out
.
println
(
“驾驶奥拓汽车”
);
}
}
同理,只需要我的驾驶员具备通用汽车 Car的驾驶能力,那驾驶所有的汽车都不是问题,因此 Drvier类的 drive()方法只要传入的参数是父类,那就具备了通用性:
public
class
Driver
{
public
void
drive
(
Car
car
)
{
// 方法参数使用父类来替代
car
.
drive
();
}
public
static
void
main
(
String
[]
args
)
{
Alto
alto
=
new
Alto
();
Driver
driver
=
new
Driver
();
driver
.
drive
(
alto
);
}
}
问题暂且解决了!
但是再后来,程序羊他好像又更发达了一些,连车都不想坐了,想买一头驴(Donkey)让司机骑着带他出行!
很明显,原先适用于汽车的 drive()方法肯定是不适合骑驴的!但我们希望聘请的这位驾驶员既会开汽车,又会骑驴怎么办呢?
害!我们干脆直接定义一个叫做交通工具( TrafficTools)的通用接口吧!里面包含一个通用的交通工具使用方法,管你是驾驶汽车,还是骑驴骑马,具体技能怎么实现先不管:
// 通用的交通工具接口定义
public
interface
TrafficTools
{
void
drive
();
// 通用的交通工具使用方法
}
有了这个接口约定,接下来就好办了。我们让所有的 Car、或者驴、马等,都来实现这个接口:
public
class
Car
implements
TrafficTools
{
@Override
public
void
drive
()
{
}
}
public
class
Wuling
extends
Car
{
public
void
drive
()
{
System
.
out
.
println
(
“驾驶五菱宏光汽车”
);
}
}
public
class
Fit
extends
Car
{
public
void
drive
()
{
System
.
out
.
println
(
“驾驶飞度汽车”
);
}
}
public
class
Alto
extends
Car
{
public
void
drive
()
{
System
.
out
.
println
(
“驾驶奥拓汽车”
);
}
}
public
class
Donkey
implements
TrafficTools
{
@Override
public
void
drive
()
{
System
.
out
.
println
(
“骑一头驴”
);
}
}
这个时候只要我们的驾驶员师傅也面向接口编程,就没有任何问题:
public
class
Driver
{
// 方法参数面向接口编程
public
void
drive
(
TrafficTools
trafficTools
)
{
trafficTools
.
drive
();
}
public
static
void
main
(
String
[]
args
)
{
Driver
driver
=
new
Driver
();
driver
.
drive
(
new
Wuling
()
);
// 开五菱
driver
.
drive
(
new
Fit
()
);
// 开飞度
driver
.
drive
(
new
Alto
()
);
// 开奥拓
driver
.
drive
(
new
Donkey
()
);
// 骑一头驴
}
}
很明显,代码完全解耦了!这就是接口带来的便利。
代码的扩展性
面向接口编程的优点远不止上面这种代码解耦的场景,在实际企业开发里,利用接口思想对已有代码进行灵活扩展也特别常见。
再举一个例子:假设程序羊有一个非常豪气的朋友,叫:程序牛,他们家出行可不坐车,全靠私人飞机出行:
// 通用的飞机飞行接口
public
interface
Plane
{
void
fly
();
}
// 程序牛的专用机长,受过专业训练(即:实现了通用飞行接口)
public
class
PlaneDriver
implements
Plane
{
@Override
public
void
fly
()
{
System
.
out
.
println
(
“专业的飞行员操控飞机”
);
}
}
// 出门旅行
public
class
Travel
{
// 此处函数参数也是面向接口编程!!!
public
void
fly
(
Plane
plane
)
{
plane
.
fly
();
}
public
static
void
main
(
String
[]
args
)
{
Travel
travel
=
new
Travel
();
// 开启一段旅行
PlaneDriver
planeDriver
=
new
PlaneDriver
();
// 聘请一个机长
travel
.
fly
(
planeDriver
);
// 由专业机长开飞机愉快的出去旅行
}
}
但是突然有一天,他们家聘请的机长跳槽了,这时候程序牛一家就无法出行了,毕竟飞机不会驾驶。
于是他跑来问我借司机,想让我的驾驶员来帮他驾驶飞机出去旅行。
我一看,由于他们的代码面向的是接口,我就肯定地答应了他!
这时候对我这边的扩展来说就非常容易了,我只需要安排我的驾驶员去培训一下飞行技能就OK了(实现一个方法就行):
// 让我的驾驶员去培训一下飞行技能(即:去实现通用飞行接口)
public
class
Driver
implements
Plane
{
public
void
drive
(
TrafficTools
trafficTools
)
{
trafficTools
.
drive
();
}
// 实现了fly()方法,这下我的驾驶员也具备操控飞机的能力了!
@Override
public
void
fly
()
{
System
.
out
.
println
(
“普通驾驶员操控飞机”
);
}
}
这时候我的驾驶员 Driver类就可以直接服务于他们一家的出行了:
public
class
Travel
{
public
void
fly
(
Plane
plane
)
{
plane
.
fly
();
}
public
static
void
main
(
String
[]
args
)
{
Travel
travel
=
new
Travel
();
// 专业飞行员操控飞机
PlaneDriver
planeDriver
=
new
PlaneDriver
();
travel
.
fly
(
planeDriver
);
// 普通驾驶员操控飞机
Driver
driver
=
new
Driver
();
travel
.
fly
(
driver
);
}
}
看到没,这一改造过程中,我们只增加了代码,却并没有修改任何已有代码,就完成了代码扩展的任务,非常符合开闭原则!
实际项目
实际开发中,我们就暂且不说诸如 Spring这种框架内部会大量使用接口,并对外提供使用,就连我们自己平时写业务代码,我们也习惯于在 Service层使用接口来进行一层隔离:
这种接口定义和具体实现逻辑的分开,非常有利于后续扩展和维护!
小结
面向接口编程开发,对代码架构的解耦和扩展确实很有好处,这种编码思想也值得平时开发结合实践反复理解和回味!