java 接口_Java 接口编程的好处-小接口,大学问

2832f44b123e2b7d98261d423d15f69a.png

接口是什么

接口,在 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 大设计原则。

  1. 单一职责原则 (Single Responsibility Principle)
  2. 开放-关闭原则 (Open-Closed Principle)
  3. 里氏替换原则 (Liskov Substitution Principle)
  4. 依赖倒转原则 (Dependence Inversion Principle)
  5. 接口隔离原则 (Interface Segregation Principle)
  6. 迪米特法则(Law Of Demeter)
  7. 组合/聚合复用原则 (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 的两个实现类。就是文章第一部分所举的例子那样。

有的同学看完想了想说,那不对呀,你这样整完之后,那和重新创建一个类的方式有什么区别,该修改的地方还是要修改呀?

1b370fd0513b1861060f017c3198c479.png

其实不然,用了接口之后,我们 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 部分,基本上都是接口定义。

616dec75b5420674b92629c4845047b5.png

然后,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,实现了这两个方法。

dc6a5694f7272d3270e27ab0250ad7c0.png

同样的,其他数据库驱动也要实现这两个方法,而我们在代码中只需要引用相关的驱动包,然后调用 DataSource getConnection 方法就可以获取数据库连接,而不用在乎到底是用了 mysql 还是其他的数据库。

另外,除了数据库连接驱动外,还有各个数据库连接池框架,比如 HikariPool、Durid 也都是通过实现各个接口来完成各自的连接池管理工作的。

3、为多种设计模式提供基础

在上面也提到了很多设计模式都离不开接口。

依赖注入模式:比如 Spring 框架的核心技术依赖注入模式,其中有一种方式就是利用接口实现的,叫做接口注入。

代理模式:Spring 中的 AOP 就是用了动态代理模式,如果启用的是 JDK 的动态代理,要被代理的类就要从一个接口实现而来。与之对应的是 CGLIB 动态代理模式,这种方式不需要接口。

其他的还有像工厂模式、适配器模式等等。

4、实现可测试的代码

当我们开发完功能后,要进行测试,但是有一些环节我们发现如果不用真实参数就运行不下去,那么如果有接口的话,我们可以实现这个接口,做一些假的模拟返回值回来,从而绕过这个步骤。

再有,比如要操作的数据是生产数据,操作具有一定的危险性。那么,我们也可以实现一个接口,做一个模拟返回。

从而达到整体功能的测试。

5、规范和安全性

有些地方说接口是为了为项目做规范,比方说,架构师负责做接口,定义方法等,从而实现结构和命名上的规范。但是,规范主要还是在于开发者自身,接口做的再标准,开发者实力不行,那项目也没办法做到规范。

还有就是安全性,假设你要使用我做的 SDK,那我把接口暴露给你就好了,接口定义、方法调用方式都写在文档里,细节不用管。就好比我前面说的,跟着跟着源代码,咔嚓进到一个接口类,这样就阻碍了一部分人的跟踪脚步。但是也只是有限的安全性,除非是远程调用(RPC)。

总结

接口为更多的设计模式提供了基础。

接口封装变化,以减小之后由于改变带来的成本。那么我们在项目中怎么判断哪些地方会有变化呢?确实没有统一的标准,大多数情况下要靠项目设计者的预估和经验。

一般来说,有交互的地方发生变化的概率更大一些。比如说 Java 和数据库交互,例如上面提到的各种数据库驱动包。还有发生数据转换的地方,比如从 DAO 层向 service 层、service 层向 controller 层往往返回的数据格式和内容发生改变的可能性更大。

当然,并不是给所有可能变化的地方都用上接口就是好的,项目初始阶段还是以简单为主,否则过度设计得不偿失。

-----------------------

公众号:古时的风筝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值