内容简介:古时的风筝接口,在 Java 中是一个抽象类型。一般来说接口只做方法的定义,不做具体的实现。
古时的风筝
接口是什么
接口,在 Java 中是一个抽象类型。一般来说接口只做方法的定义,不做具体的实现。
不过在 Java 8 之后,接口类中可以定义静态变量,也可以做静态方法的实现,并且可以用 default 关键字修饰普通方法,用 default 修饰后,就可以加上方法的实现了。不过这不在今天的讨论范围内。
我们要应用一个接口,通常称作实现接口,用关键字 implements
表示,实现类必须实现接口类中定义的所有方法,并用 @Override
注解表示。
我们在日常的开发中会经常接触到接口类,如果是使用 Spring 框架的话, 通常项目结构上会按照 MVC 方式分层,在 service 层,通常是一个服务接口类对应一个服务实现类。
除此之外,在各个开源框架中,比如 Spring、Dubbo、MyBatis、Netty 这些,接口也是无所不在。我们在看一些开源框架代码的时候,正满眼放光一步一步往下跟代码不亦乐乎的时候,咔嚓就进了一个接口类中,只看到方法定义,看不到方法具体实现,然后就跟不下去了,然后还得回过头去看到底是使用的哪个具体实现类。是不是经常有这种情况。这时候就会在心里默念,接口有啥好的,严重阻碍了我都学习积极性(手动狗头)。
下面是一个接口类的定义,一个对键值对格式化的接口类,方法只有一个就是 format。
public interface DataFormatter {
/**
* 键值对格式化
* @param key
* @param value
* @return
*/
String format(String key,String value);
}
下面是两个具体的实现类,实现了 json 和 properties 两种格式化方式。
/**
* JsonDataFormatter
* json 格式化
* @author fengzheng
* @date 2020/3/11
*/
public class JsonDataFormatter implements DataFormatter {
@Override
public String format(String key, String value) {
return String.format("{\"%s\":\"%s\"}", key, value);
}
}
/**
* PropertiesDataFormatter
* properties 格式化
* @author fengzheng
* @date 2020/3/11
*/
public class PropertiesDataFormatter implements DataFormatter {
@Override
public String format(String key, String value) {
return String.format("%s=%s", key, value);
}
}
之后我们想使用哪种格式化方式,就实例化哪个实现类,然后调用 format 方法。
public class DataFormatTest {
public static void main(String[] args){
String key = "name";
String value = "古时的风筝";
DataFormatter jsonDataFormatter = new JsonDataFormatter();
//{"name":"古时的风筝"}
System.out.println(jsonDataFormatter.format(key,value));
//name=古时的风筝
DataFormatter propertiesFormatter = new PropertiesDataFormatter();
System.out.println(propertiesFormatter.format(key,value));
}
}
这个定义和使用过程恐怕在座的各位都再熟悉不过了,那么到底使用接口有什么好处呢,为什么非要把方法定义和方法实现分开呢,难道不是多此一举吗。
接下来,我将列举几个使用接口的好处,可能还有更多,欢迎补充。
接口编程的好处
通俗的来说,接口最大的好处就是可以最大限度的降低「 改变 」带来的影响,封装「 变化 」的部分。
往高维度里说,那就要牵扯出 设计模式 了,一个系统架构如果设计的好,那就一定离不开各种设计模式。而好的设计模式一般都遵循如下 7 大设计原则。
-
单一职责原则 (Single Responsibility Principle)
-
开放-关闭原则 (Open-Closed Principle)
-
里氏替换原则 (Liskov Substitution Principle)
-
依赖倒转原则 (Dependence Inversion Principle)
-
接口隔离原则 (Interface Segregation Principle)
-
迪米特法则(Law Of Demeter)
-
组合/聚合复用原则 (Composite/Aggregate Reuse Principle)
重点来了,在 Java 中要实现这 7 大原则,那必定离不开接口。好多的设计模式都是通过接口实现的。
接口都这么抽象了,我们就没必要说的这么玄幻了,我们就通俗点儿说吧。
1、实现了松耦合
我在文章第一部分定义了一个键值对格式化的接口,我们还用键值对格式化这个功能举例子。假设我开始并没有定义一个接口,而是定义了一个普通的类。比如下面这个,按照 json 格式返回字符串。然后,愉快的项目中使用了这个格式化方法。
public class JsonDataFormatter {
public String format(String key, String value) {
return String.format("{\"%s\":\"%s\"}", key, value);
}
}
按照故事的发展,当然是后来发生了一点变故,我的某个模块它变了,它不想要 json 格式了,想要 properties 格式了,没错,就是这么善变。
这下慌了,这怎么办,直接修改 format 方法吧,肯定不行,有的模块还是要 json 格式。新添加一个类吧,实现一个 properties 格式化的方法。
public class PropertiesDataFormatter {
public String format(String key, String value) {
return String.format("%s=%s", key, value);
}
}
可以是可以,但是要对这个模块中已经调用了 json 格式化方法的地方一一做修改,你愿意这么干么,当然不愿意。
那怎么办呢,没错,定义接口,然后实现两个针对 json 和 properties 的两个实现类。就是文章第一部分所举的例子那样。
有的同学看完想了想说,那不对呀,你这样整完之后,那和重新创建一个类的方式有什么区别,该修改的地方还是要修改呀?
其实不然,用了接口之后,我们 new 出来的实现类会被接口类型接收,就像下面这样:
DataFormatter jsonDataFormatter = new JsonDataFormatter();
DataFormatter propertiesFormatter = new PropertiesDataFormatter();
最终接收的类型都是 DataFormatter
,而不是具体的实现类的类型,这样一来就省去了很多事儿,否则光 import 的修改就一大堆。
当然,这并不是最优解,最好的办法是结合工厂模式,让工厂类帮你返回具体的实现类,比如下面这个实例代码这样。
public static DataFormatter create(){
return new JsonDataFormatter();
}
DataFormatter jsonDataFormatter = create();
2、增强了扩展性
其实上面的键值对格式化的例子也有涉及到扩展性的地方。再进一步,我又在系统中加了个模块,这个模块也有键值对格式化,但是要使用特殊的格式化方式,比如 “name 是 古时的风筝” 。用了接口就简单了,增加一个实现类,实现自 DataFormatter
接口。
之后不管你新加的模块用什么样的格式化方式,你只要实现接口就行了。
最常见的就是数据库操作这块,假设我之前用的是 mysql 数据库,后来呢,数据量增加了,有些部分用上了 hbase 和 mongodb。
那怎么办,改代码吗?那简直要了亲命了。
各种数据库驱动包就是利用接口做的,Java 我只管定义接口和方法声明,你们拿去用,自己实现具体的细节。下面是 JDK 中 SQL 部分,基本上都是接口定义。
然后,mysql 提供了 mysql-connector-java
驱动包,实现了 mysql 相关的操作。其他的提供商实现自家数据库的驱动包,但都要实现 JDK 定义的接口方法。
下面是 JDK 中数据库连接的接口定义,定义了两个方法 getConnection(),一个带参数,一个不带参数。
package javax.sql
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
在 mysql-connector-java
包中,我们看到了实现此接口的类 MysqlDataSource
,实现了这两个方法。
同样的,其他数据库驱动也要实现这两个方法,而我们在代码中只需要引用相关的驱动包,然后调用 DataSource
getConnection 方法就可以获取数据库连接,而不用在乎到底是用了 mysql 还是其他的数据库。
另外,除了数据库连接驱动外,还有各个数据库连接池框架,比如 HikariPool、Durid 也都是通过实现各个接口来完成各自的连接池管理工作的。
3、为多种设计模式提供基础
在上面也提到了很多设计模式都离不开接口。
依赖注入模式:比如 Spring 框架的核心技术依赖注入模式,其中有一种方式就是利用接口实现的,叫做接口注入。
代理模式:Spring 中的 AOP 就是用了动态代理模式,如果启用的是 JDK 的动态代理,要被代理的类就要从一个接口实现而来。与之对应的是 CGLIB 动态代理模式,这种方式不需要接口。
其他的还有像工厂模式、适配器模式等等。
4、实现可测试的代码
当我们开发完功能后,要进行测试,但是有一些环节我们发现如果不用真实参数就运行不下去,那么如果有接口的话,我们可以实现这个接口,做一些假的模拟返回值回来,从而绕过这个步骤。
再有,比如要操作的数据是生产数据,操作具有一定的危险性。那么,我们也可以实现一个接口,做一个模拟返回。
从而达到整体功能的测试。
5、规范和安全性
有些地方说接口是为了为项目做规范,比方说,架构师负责做接口,定义方法等,从而实现结构和命名上的规范。但是,规范主要还是在于开发者自身,接口做的再标准,开发者实力不行,那项目也没办法做到规范。
还有就是安全性,假设你要使用我做的 SDK,那我把接口暴露给你就好了,接口定义、方法调用方式都写在文档里,细节不用管。就好比我前面说的,跟着跟着源代码,咔嚓进到一个接口类,这样就阻碍了一部分人的跟踪脚步。但是也只是有限的安全性,除非是远程调用(RPC)。
总结
接口为更多的设计模式提供了基础。
接口封装 变化 ,以减小之后由于改变带来的成本。那么我们在项目中怎么判断哪些地方会有变化呢?确实没有统一的标准,大多数情况下要靠项目设计者的预估和经验。
一般来说,有交互的地方发生变化的概率更大一些。比如说 Java 和数据库交互,例如上面提到的各种数据库驱动包。还有发生数据转换的地方,比如从 DAO 层向 service 层、service 层向 controller 层往往返回的数据格式和内容发生改变的可能性更大。
当然,并不是给所有可能变化的地方都用上接口就是好的,项目初始阶段还是以简单为主,否则过度设计得不偿失。
还可以读:
-----------------------
公众号:古时的风筝
一个斜杠程序员,一个纯粹的技术公众号,多写 Java 相关技术文章,不排除会写其他内容。
【我要升华!】
以上所述就是小编给大家介绍的《为什么要使用接口编程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- JStorm 源码解析:编程接口
- 面向接口编程,你考虑过性能吗?
- linux socket编程-socket接口
- 为什么我们要面向接口编程
- Golang之面向接口编程及使用分析
- golang 面向接口编程的知识点讲解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。