|
|
|
---
|
|
|
|
title: 面向接口编程-转载
|
|
|
|
author: TianZD
|
|
|
|
top: true
|
|
|
|
cover: true
|
|
|
|
toc: true
|
|
|
|
mathjax: false
|
|
|
|
summary: 面向接口编程思想学习笔记,粗略学了一下,没有参考价值
|
|
|
|
tags:
|
|
|
|
- 编程思想
|
|
|
|
- 接口
|
|
|
|
categories:
|
|
|
|
- java
|
|
|
|
reprintPolicy: cc_by
|
|
|
|
abbrlink: '14e0017'
|
|
|
|
date: 2022-04-29 10:58:42
|
|
|
|
coverImg:
|
|
|
|
img:
|
|
|
|
password:
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 为什么我们要面向接口编程?!
|
|
|
|
|
|
|
|
转载自程序羊的知乎文章
|
|
|
|
|
|
|
|
# 到底面向?编程
|
|
|
|
|
|
|
|
**面向过程编程(`Procedure Oriented`、简称`PO`)** 和 **面向对象编程(`Object Oriented`、简称`OO`)** 我们一定听过,然而实际企业级开发里受用更多的一种编程思想那就是:**面向接口编程(`Interface-Oriented`)**!
|
|
|
|
|
|
|
|
接口这个概念我们一定不陌生,实际生活中**最常见的例子就是**:插座!
|
|
|
|
|
|
|
|
我们只需要事先定义好插座的**接口标准**,各大插座厂商只要按这个接口标准生产,管你什么牌子、内部什么电路结构,这些均和用户无关,用户拿来就可以用;而且即使插座坏了,只要换一个符合接口标准的新插座,一切照样工作!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![img](https://pic1.zhimg.com/80/v2-91a154ce20b13b02a4758f51931388c4_1440w.jpg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
同理,实际代码设计也是这样!
|
|
|
|
|
|
|
|
我们在设计一个软件的代码架构时,我们都希望**事先约定**好各个功能的**接口**(即:约定好接口签名和方法),实际开发时我们只需要实现这个接口就能完成具体的功能!后续即使项目变化、功能升级,程序员只需要按照接口约定重新实现一下,就可以达到系统升级和扩展的目的!
|
|
|
|
|
|
|
|
正好,Java中天生就有`interface`这个语法,这简直是为面向接口编程而生的!
|
|
|
|
|
|
|
|
所以接下来落实到代码上,举个通俗一点的小例子唠一唠,实际业务代码虽然比这个复杂,但原理是一模一样的。
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
## 做梦了
|
|
|
|
|
|
|
|
假如哪一天程序羊真发达了,一口豪气买了两辆豪车,一辆五菱宏光、一辆飞度、并且还专门聘请了一位驾驶员来帮助驾驶。
|
|
|
|
|
|
|
|
**两辆豪车在此:**
|
|
|
|
|
|
|
|
```java
|
|
|
|
public class Wuling {
|
|
|
|
public void drive() {
|
|
|
|
System.out.println("驾驶五菱宏光汽车");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public class Fit {
|
|
|
|
public void drive() {
|
|
|
|
System.out.println("驾驶飞度汽车");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**驾驶员定义在此:**
|
|
|
|
|
|
|
|
驾驶员定义了两个`drive()`方法,分别用来驾驶两辆车:
|
|
|
|
|
|
|
|
```text
|
|
|
|
public 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()`方法,具体怎么开先不用管:
|
|
|
|
|
|
|
|
```text
|
|
|
|
// 抽象的汽车类Car,代表所有汽车
|
|
|
|
public class Car {
|
|
|
|
void drive() { } // 通用的汽车驾驶方法
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
这时,只要我新买的奥拓符合`Car`定义的驾驶标准即可被我的驾驶员驾驶,所以只需要新的奥拓来继承一下`Car`类即可:
|
|
|
|
|
|
|
|
```text
|
|
|
|
public class Alto extends Car {
|
|
|
|
public void drive() {
|
|
|
|
System.out.println("驾驶奥拓汽车");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
同理,只需要我的驾驶员具备通用汽车`Car`的驾驶能力,那驾驶所有的汽车都不是问题,因此`Drvier`类的`drive()`方法只要传入的参数是**父类**,那就具备了通用性:
|
|
|
|
|
|
|
|
```text
|
|
|
|
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`)的通用接口吧!里面包含一个通用的交通工具使用方法,管你是驾驶汽车,还是骑驴骑马,具体技能怎么实现先不管:
|
|
|
|
|
|
|
|
```text
|
|
|
|
// 通用的交通工具接口定义
|
|
|
|
public interface TrafficTools {
|
|
|
|
void drive(); // 通用的交通工具使用方法
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
有了这个**接口约定**,接下来就好办了。我们让所有的`Car`、或者驴、马等,都来实现这个接口:
|
|
|
|
|
|
|
|
```text
|
|
|
|
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("骑一头驴");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
这个时候只要我们的驾驶员师傅也**面向接口编程**,就没有任何问题:
|
|
|
|
|
|
|
|
```text
|
|
|
|
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() ); // 骑一头驴
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
很明显,代码完全解耦了!这就是接口带来的便利。
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
## 代码的扩展性
|
|
|
|
|
|
|
|
面向接口编程的优点远不止上面这种代码解耦的场景,在实际企业开发里,利用接口思想**对已有代码进行灵活扩展**也特别常见。
|
|
|
|
|
|
|
|
再举一个例子:假设程序羊有一个非常豪气的朋友,叫:**程序牛**,他们家出行可不坐车,全靠私人飞机出行:
|
|
|
|
|
|
|
|
```text
|
|
|
|
// 通用的飞机飞行接口
|
|
|
|
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了(实现一个方法就行):
|
|
|
|
|
|
|
|
```text
|
|
|
|
// 让我的驾驶员去培训一下飞行技能(即:去实现通用飞行接口)
|
|
|
|
public class Driver implements Plane {
|
|
|
|
|
|
|
|
public void drive( TrafficTools trafficTools ) {
|
|
|
|
trafficTools.drive();
|
|
|
|
}
|
|
|
|
|
|
|
|
// 实现了fly()方法,这下我的驾驶员也具备操控飞机的能力了!
|
|
|
|
@Override
|
|
|
|
public void fly() {
|
|
|
|
System.out.println("普通驾驶员操控飞机");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
这时候我的驾驶员`Driver`类就可以直接服务于他们一家的出行了:
|
|
|
|
|
|
|
|
```text
|
|
|
|
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`层使用接口来进行一层隔离:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
![img](https://pic2.zhimg.com/80/v2-a41981e2872a399651bfc002e601a5a5_1440w.jpg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这种接口定义和具体实现逻辑的分开,非常有利于后续扩展和维护!
|
|
|
|
|
|
|
|
------
|
|
|
|
|
|
|
|
## 小结
|
|
|
|
|
|
|
|
面向接口编程开发,对代码架构的解耦和扩展确实很有好处,这种编码思想也值得平时开发结合实践反复理解和回味!
|