两月前论及到 针对接口编程及敏捷编程,如今再一次老调重提。面向对象有一个很重要设计原则:
针对接口编程,而不是针对实现编程
这一原则关键在于理解什么是接口,请参照前文提到的上一篇。因为 Java 语言中有 interface 这个概念,于是 Java 的 interface 便躺枪了,好好的针对接口编程在 Java 中就机械式的变成了针对 interface 的编程。
以致于只要有实现的地方都可能变成像 UserDao.java(接口) 与 UserDaoImpl.java 成对出现,在以后的重构中基本上是为了实现而修改接口,这也进一步违背了针对接口编程的初衷,接口怎么一点也不稳固。
或许有人认为先声明一个 interface,然后我们可能会有多种实现(为将来考虑,不过多数时候只会是单一实现),这很像是 interface 存在的一个理由。可是当我们写下类似 UserDaoImpl 这个样的实现类名时直接宣告了该接口其实就只有一种实现,不然的话再加一个实现的话类名为哪般?UserDaoImpl2? 显然多种实现时 UserDaoImpl 需要重命名了。
既然 UserDao(interface) + UserDaoImpl 这种模式下,首先 UserDao 无法称之为真正的接口,并且有新实现加入时不可避免要重构,那何不在只有一种实现是先只需声明 UserDao 实现类即可,待到有新实现时抽取出接口来。这才应该是最小化,快速敏捷式编程。
UserDao(interface) + UserDaoImpl 也并非总是有问题,当 UserDao 是一个真正的外部接口(稳固的,而向外部系统的协议),而它只有一种实现的话实现类名命作 UserDaoImpl 并无异议。
但是通常我们的 UserDao(interface) + UserDaoImpl 模式只是把更新先进了 Java 语言设计倒退为 C++ 的头文件与源文件的分离行为。C++ 由于语言本身的特性需要把方法声明与实现分别写在 .h 和 .cpp 文件中去,#include 时我们只会包含 .h 文件。
与 C++ 类比发现,如果我们机械式的认为 UserDao(interface) + UserDaoImpl 就是在做针对接口编程,那不过是傻傻地把 UserDao(interface) 当成了 C++ 的 .h 文件, UserDaoImpl 当成了 C++ 的 .cpp 文件而已。
Java 的 import 可比 C/C++ #include 智能,重复交叉引用都不是事,所以并不存在一个真正接口的情况下,完全可以只声明一个 UserDao 类,依照整洁代码的要求把 public 方法前移别人自然就清楚 UserDao 类应如何使用。public 方法就是被暴露的他人可以自由调用的方法,无须勉强的通过一个 Java interface, 像 C++ 的 .h 文件那样来暴露方法。
最后想像一下,在 UserDao(interface) + UserDaoImpl 模式下,UserDaoImpl 除了应该现的接口方法外,其余方法都必须是 private 了,否则就尴尬了。当然也可以把 private 去掉变成 package-private, 以此向它的单元测试作出让步。
2017-08-17: 重新理解,纠正一下上面那一句,应该说 UserDao(interface) + UserDaoImpl 模式对测试应该是更友好的,因为使用者关注接口中的 public 方法,而在 *Impl 类中的非接口方法可以放心的变为 package-private, 方便于测试,还不影响别人怎么调用。只有一个实现类的话,package-private 可能被别人直接调用了。
本文链接 https://yanbin.blog/rough-interface-based-programming/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。