相关阅读:
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. 通过接口制订标准例子
注意:
- 本例目的仅为说明接口可用于制订标准,进而可采用相同的模式来处理类似逻辑,这样易于后续新开发和维护同类功能,本例并未完整实现。
- 另外为了简单,本例都写成了内部类和接口。
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. 总结:如何面向接口编程
- 首先透过接口的本质:是否对外提供接口屏蔽内部实现,是否有变化点,是否要制订标准以方便维护,这三个方面来确定是否需要定义接口。
- 一般来说要用接口是因为事物既具有共性又有不同点,要能找到共性又具有不同点才需要定义接口,例如飞机和鸟都能飞,这是共性,但飞的方式不同,这是不同点,这个寻找共性的过程就是抽象的过程,抽象能力高则面向接口编程能力就高。
- 接口不是必须的,当拿不定主意是否需要接口时通常有两个可能:“确实不需要接口” 和 “当下你还没法确定是否需要接口”,这时也不必勉强,等发现需要时再重构。
end.
<--阅过留痕,左边点赞 !