面向对象编程——世界专车公司(一):按接口编程
序言
面向对象编程(简称:OOP)要求我们按人的思考方式来解决程序设计中的各类关联问题。这种编程的好处使得对象之间具有某种独立性,否则可能会奇怪的出现在你家里长期住着一个邋遢的陌生人的情景,而真实的世界是不应该有这样的情况的。这种好的独立性,使得对象之间必须联系的时候才进行联系,这会使得编写的代码易于重用、维护及扩展。
我们从简单工厂(Simple Factory)开始OOP的主题,为了形象的进行讲述,我们假想一家专车公司,称为世界专车公司(World Special Inc),我们想在这一行竞争中脱颖而出,成为世界第一(Number One),必须让我们的乘客客户获得更好,更快,更安全的服务。
按接口编程
说明:程序的代码往往都是同一个团队写的,修改代码对你们来说非常方便,从而很容易忽视面向对象编程的方式。记住程序里面表示的外界对象,要按它们的真实情景来思考。比如代表司机的Driver类,就应该按Driver类是司机的方式来思考自己本身及与其他对象的联系。
我们的专车公司当前有三类车:奔驰车(Bens)、宝马车(BWM, “别摸我”)、奥迪车(Audi),每辆车都会有一个车牌号(Plate No)。
Bens :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package object.world.special;
public class Bens {
private String plateNo ; // 车牌号码
public Bens(String plateNo){
this.plateNo = plateNo ;
}
public void openKey(){
System.out.println("车牌号码 " + plateNo + " 的Bens车的锁打开了");
}
public void start(){
System.out.println("车牌号码 " + plateNo + " 的Bens车启动了");
}
public void run(){
System.out.println("车牌号码 " + plateNo + " 的Bens车在路上");
}
}
BWM :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package object.world.special;
public class BWM {
private String plateNo ; // 车牌号码
public BWM(String plateNo){
this.plateNo = plateNo ;
}
public void openKey(){
System.out.println("车牌号码 " + plateNo + " 的BWM车的锁打开了");
}
public void start(){
System.out.println("车牌号码 " + plateNo + " 的BWM车启动了");
}
public void run(){
System.out.println("车牌号码 " + plateNo + " 的BWM车在路上");
}
}
Audi :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package object.world.special;
public class Audi {
private String plateNo ; // 车牌号码
public Audi(String plateNo){
this.plateNo = plateNo ;
}
public void openKey(){
System.out.println("车牌号码 " + plateNo + " 的Audi车的锁打开了");
}
public void start(){
System.out.println("车牌号码 " + plateNo + " 的Audi车启动了");
}
public void run(){
System.out.println("车牌号码 " + plateNo + " 的Audi车在路上");
}
}
Driver(世界专车公司的司机员工):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package object.world.special;
public class Driver {
private String staffNo ; // 员工编号
private Bens bens ;
public Driver(String staffNo , Bens bens){
this.staffNo = staffNo;
this.bens = bens ;
}
public void drive(){
System.out.println("员工编号为 "+ staffNo + " 司机在驾驶汽车");
bens.openKey();
bens.start();
bens.run();
}
public void change(Bens otherBens){
bens = otherBens;
}
}
乘客开始打专车服务:1
2
3
4
5
6
7
8
9
10
11
12
13
14package object.world.special;
public class ModelClient {
public static void main(String[] args) {
// 一位驾驶 Bens(奔驰)的驾驶员
Driver driver = new Driver("WS0001" ,new Bens("苏E10001"));
driver.drive();
// 给司机换辆 Bens(奔驰)
driver.change(new Bens("苏E10002"));
driver.drive();
}
}
让我们看看输出的结果是什么?1
2
3
4
5
6
7
8
9员工编号为 WS0001 司机在驾驶汽车
车牌号码 苏E10001 的Bens车的锁打开了
车牌号码 苏E10001 的Bens车启动了
车牌号码 苏E10001 的Bens车在路上
-------- 人为的分割线 --------
员工编号为 WS0001 司机在驾驶汽车
车牌号码 苏E10002 的Bens车的锁打开了
车牌号码 苏E10002 的Bens车启动了
车牌号码 苏E10002 的Bens车在路上
我们让司机换辆 BWM 上路 : driver.change(new BWM("苏E80001"));
结果发现代码根本编译不过,因为从Driver类里注意到这个司机有一个Bens属性,这表明这司机其实只会驾驶Bens车。这显然不符合实际情况,不是我们所想要的,专车公司要的是会驾驶各类小汽车的员工。
设计原则:针对接口编程,而不是针对实现编程
我们修改代码,不应该把Driver驾驶的汽车类别固定死,而采用按接口编程。在Java语言里面,针对接口编程有两种方式:
一般如果有很多基本的方法(method)子类都可以通用,往往继承抽象超类的方式,把那些基本的方法放入抽象超类。我们在这里采用实现接口的方式来处理,引入一个 Car 接口,接口里面的方法都是抽象的,并且是 public 的, 实现接口里面的类的方法则会具有多态(polymorphic)性:执行时会按照实际状态来执行真正的行为(Java程序其实指的是方法)。接口 Car 可以这样设计:
Car:1
2
3
4
5
6
7package object.world.special;
public interface Car {
void openKey();
void start();
void run();
}
我们发现接口里面的三个方法名与前面三类车的方法名是相同的,这是因为我们的类需要实现接口的缘故。修改后实现接口 Car 的 Bens 来看一下代码(BWM,Audi 一样处理)
Bens:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package object.world.special;
public class Bens implements Car{
private String plateNo ; // 车牌号码
public Bens(String plateNo){
this.plateNo = plateNo ;
}
public void openKey(){
System.out.println("车牌号码 " + plateNo + " 的Bens车的锁打开了");
}
public void start(){
System.out.println("车牌号码 " + plateNo + " 的Bens车启动了");
}
public void run(){
System.out.println("车牌号码 " + plateNo + " 的Bens车在路上");
}
}
只在 class 这一行添加了 implements Car
,意思是要实现 Car 里面的方法,这三个方法一开始就有了,如果 Car 添加新的方法,则 Bens 需要添加同样方法名的方法来实现。这样Bens(奔驰)、BWM(宝马)、Audi(奥迪)针对 Car 接口都有了自己不同的实现。
接下来要修改Driver(司机)类,让司机会驾驶所有的小汽车(Car),把 Bens 替换为 Car 即可。
Driver:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package object.world.special;
public class Driver {
private String staffNo ; // 员工编号
private Car car ;
public Driver(String staffNo , Car car){
this.staffNo = staffNo;
this.car = car ;
}
public void drive(){
System.out.println("员工编号为 "+ staffNo + " 司机在驾驶汽车");
car.openKey();
car.start();
car.run();
}
public void change(Car otherCar){
car = otherCar;
}
}
乘客继承打专车服务:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package object.world.special;
public class ModelClient {
public static void main(String[] args) {
// 一位驾驶 Bens(奔驰)的驾驶员
Driver driver = new Driver("WS0001" ,new Bens("苏E10001"));
driver.drive();
// 给司机换辆 Bens(奔驰)
driver.change(new Bens("苏E10002"));
driver.drive();
// === 新添加的代码 ===
// 给司机换辆 BWM(别摸我)
driver.change(new BWM("苏E80001"));
driver.drive();
// 给司机换辆 Audi(奥迪)
driver.change(new Audi("苏E60001"));
driver.drive();
}
}
先让我们看看这次输出的结果是什么?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19员工编号为 WS0001 司机在驾驶汽车
车牌号码 苏E10001 的Bens车的锁打开了
车牌号码 苏E10001 的Bens车启动了
车牌号码 苏E10001 的Bens车在路上
-------- 人为的分割线 --------
员工编号为 WS0001 司机在驾驶汽车
车牌号码 苏E10002 的Bens车的锁打开了
车牌号码 苏E10002 的Bens车启动了
车牌号码 苏E10002 的Bens车在路上
- 人为的分割线 ,底下为新的代码 ----
员工编号为 WS0001 司机在驾驶汽车
车牌号码苏E80001的BWM车的锁打开了
车牌号码苏E80001的BWM车启动了
车牌号码苏E80001的BWM车在路上
-------- 人为的分割线 --------
员工编号为 WS0001 司机在驾驶汽车
车牌号码苏E60001的Audi车的锁打开了
车牌号码苏E60001的Audi车启动了
车牌号码苏E60001的Audi车在路上
我们发现 ModelClient类的main方法 前面的代码并不需要做任何修改,另外这三类车,Driver 里面的 Car 属性都能够接受,同样是 driver.drive();
的代码,然而可以根据具体是什么车来实现相应的行为,宝马车(BMW)展示的就是宝马车的行为,奥迪车(Audi)展示的就是奥迪车的行为,这就是接口编程的好处,使得司机轻松驾驶每一种类型的车。
参考:Head First 设计模式(中文版)—— 中国电力出版社 ISBN:978-7-508-35393-7