JAVA编程思想(二)如何面向接口编程

3,924 阅读5分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 62 篇原创文章

相关阅读:

JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(五)事件通知模式解耦过程
Java编程思想(六)事件通知模式解耦过程
Java编程思想(七)使用组合和继承的场景
JAVA基础(三)ClassLoader实现热加载
Java并发编程入门(十一)限流场景和Spring限流器实现
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则

1. 接口的本质

接口封装了细节

接口对调用者封装了实现细节,调用者只需按接口规范使用,而不关心怎么实现,例如我们最常见的数据库连接接口Connection,当通过某个工具包获取Connection后(如HikariPool或者阿里Druid),调用者并不关心实际获取到的是厂家提供的驱动类,还是被工具包封装之后的代理类。

有接口就意味着有变化点

有接口就意味着有变化点,如果没有变化点,也就不需要定义接口,按照固定逻辑实现即可,照此反推,即-有可能发生变化的逻辑,才适合定义接口。例如Java的集合框架,List,Map,Set都有很多不同实现类,不同的实现类便是不同的变化点,可以满足不同的需要。

接口是一套标准

接口是一套标准,例如插座,手机充电口,插入插座的部分并没有什么不同(注意:这里仅说的是插入的部分),从这个角度看接口就是一个标准,只要符合标准就能适配接口,因此并不是一定要有变化点才能定义接口,标准的接口是为了更加规范,通用。

综上,就是接口的本质,透过这些本质,就很容易知道什么时候需要定义接口。

2. 面向接口编程的优点

从接口的本质实际已经可以看出面向接口编程的优点:

A. 接口稳定,易于扩展
B. 替换接口实现时,修改代价小
C. 代码规范,易于维护

关于接口编程如何易于扩展,请参考:JAVA编程思想(一)通过依赖注入增加扩展性

3. 接口调用方式

一般情况下有两种方式:

3.1 入参为接口

提供者提供的方法入参或构造器入参定义为接口,由调用者自行确定实现类,此时调用者要关心接口实现,并可能自行提供最佳实现。

3.2 工厂类生成接口实例

提供者提供的方法中通过工厂类生成接口实例,调用者使用该接口实例,不关心具体实现,例如数据库连接类Connection,通常都是通过工厂类生成给调用者使用。

以上两种调用方式也决定了具体的实现方式。

4. 面向接口编程举例

在接口的本质中已经提到,当要对外屏蔽实现,功能有变化点,或制订标准时需要定义接口,前两个的的例子很常见,就不再举例,这里仅举例说明接口是如何用于制订标准的。

4.1. 通过接口制订标准例子

注意:

  1. 本例目的仅为说明接口可用于制订标准,进而可采用相同的模式来处理类似逻辑,这样易于后续新开发和维护同类功能,本例并未完整实现。
  2. 另外为了简单,本例都写成了内部类和接口。
import java.util.List;

public class StandardDemo
{
    
    public static void main(String[] args)
    {
        // 在这种场景下,实际使用时一定会引用到实现类,并没有屏蔽实现细节,此处接口的作用就是统一标准方法,方便维护。
        DAOInterface<Person, PrimaryKey> dao = new PersonDAO<>();
        
        // 以下代码并没有完整实现,仅仅用于示例这个标准接口是如何使用的
        List<Person> people = dao.query(new Condition());
        Person person = dao.queryByPrimaryKey(new PrimaryKey());
        dao.update(new Person());
    }
    
}

// 这个接口的主要目的是:定义一套标准接口方法,要求所有数据库访问都遵循这些标准接口方法来实现,使得统一模式之后更易于维护,而不是各个开发人员各写一套出来。
interface DAOInterface<T, K>
{
    List<T> query(Condition condition);
    
    T queryByPrimaryKey(K entiryKey);
    
    int update(T entity);
    
    int create(T entity);
}

class PersonDAO<Person, PrimaryKey> implements DAOInterface<Person, PrimaryKey>
{
    @Override
    public List<Person> query(Condition condition)
    {
        return null; // 仅示例,没有完整实现
    }
    
    @Override
    public Person queryByPrimaryKey(PrimaryKey primaryKey)
    {
        return null; // 仅示例,没有完整实现
    }
    
    @Override
    public int update(Person entity)
    {
        return 0; // 仅示例,没有完整实现
    }
    
    @Override
    public int create(Person entity)
    {
        return 0; // 仅示例,没有完整实现
    }
}

class StudentDAO<Student, PrimaryKey> implements DAOInterface<Student, PrimaryKey>
{
    @Override
    public List<Student> query(Condition condition)
    {
        return null; // 仅示例,没有完整实现
    }
    
    @Override
    public Student queryByPrimaryKey(PrimaryKey primaryKey)
    {
        return null; // 仅示例,没有完整实现
    }
    
    @Override
    public int update(Student student)
    {
        return 0; // 仅示例,没有完整实现
    }
    
    @Override
    public int create(Student student)
    {
        return 0; // 仅示例,没有完整实现
    }
}

// 仅示例,没有完整实现
class Student
{
}

// 仅示例,没有完整实现
class Person
{
}

// 查询条件类,并未实现
class Condition
{
}

// 主键类,并未实现
class PrimaryKey
{
}

5. 总结:如何面向接口编程

  1. 首先透过接口的本质:是否对外提供接口屏蔽内部实现,是否有变化点,是否要制订标准以方便维护,这三个方面来确定是否需要定义接口。
  2. 一般来说要用接口是因为事物既具有共性又有不同点,要能找到共性又具有不同点才需要定义接口,例如飞机和鸟都能飞,这是共性,但飞的方式不同,这是不同点,这个寻找共性的过程就是抽象的过程,抽象能力高则面向接口编程能力就高。
  3. 接口不是必须的,当拿不定主意是否需要接口时通常有两个可能:“确实不需要接口” 和 “当下你还没法确定是否需要接口”,这时也不必勉强,等发现需要时再重构。

end.


<--阅过留痕,左边点赞 !