JAVA在设计接口和类的规则时,有一个明确的规定。JAVA不支持类(实现)的多重继承,但支持接口(定义)的多重继承。
我已经无从了解这个设计的初衷,但这样的规定隐含了以下的意义。
接口是设计的产物,即在需求设计时定义的对软件功能的定义。而类是实现的产物,它是在实现过程中根据实现的具体情况而完成的。如果用代码来说明就是:
在设计时我需要我设计的“模块”提供两个功能:
1.提供两个整数相加的功能。
2.提供两个字符串连接的功能。
我是一个伟大的设计师,为了不影响我的整体思路,我不会在这时停下来去实现它,所以我需要一个类似伪代码的东西来记录我的实现思路。java说,好,我们为你提供一个叫接口的东西,你只写下你要实现的东西不用提供具体实现方法。
- interface IMyFunc {
-
- public int add(int a, int b);
-
- public String concat(String str1, String str1);
- }
。。。。。。。其它设计。
好了,哪个小匪来领实现这个任务?
小匪甲领取了这个编码任务。分析大当家的设计思想,啊这个好办。
- class MyMathImp implements IMyFunc{
-
- public int add(int n1,int n2) {
-
- //因为传进来的都是整数,如果不是整数在编译时就出错了,
- //所以我不用检查,直接的相加,哈哈,今天工资混到手了。
-
- return n1 + n2;
-
- }
-
-
-
- public String concat(String s1,Strings2) {
-
- //哎,这个,这个好象要检查一下吧,如果有一个字符串是null,
- //我是显示"字符串null"还是显示"字符串"呢?如果两个都为null,
- //我是显示一个"null"还是显示"nullnull"呢?
- //真复杂,严密性比具体实现还复杂,我干脆把它放到另一个方法中吧。
-
- return checkedConcat(s1,s2);
- }
-
- String checkedConcat (String s1,String s2){
- if(s1 == null && s2 == null) return "null";
- if(s1 == null) s1 = "null";
- if(s2 == null) s2 = "null";
- return s1 + s2;
- }
-
- }
checkedConcat我还想被别的地方调用,所以我不想设计成ptivate的。所以我设计的MyModuleImp从外面可以看到三个方法。
大当家的叫我提供两个方法,而我的实现有三个方法,幸亏有接口啊,我发布这个模块时只提供接口的文档。这样你在智能感应的IDE中一打开这个接口看到的只是大当家定义的两个方法。
所以:接口可以只将设计意图的方法暴露给调用者。
那么,当我一个类提供了不同功能的方法时,我想在不同时候只显示某类功能的方法,怎么办?
其实这个问题Sun的代码中都没有很好地处理。比如我的一个类提供了对象的初始化方法和对象释放的方法,整数运算的方法和字符串运算方法以及图形运算方法。当然这只是举例。
有人说这个类设计不合理,这么多不同类型的功能应该在不同类中实现。
说得对!但是..........
如果我们的对象是本地生成的,多次new多个对象成本不是太高,如果我们的对象是从远程调用的,那么多个对象的生成成本就很高了。
另外任何对象自身管理的方法和业务方法不能分到两个类中实现吧?一个只有自身管理的方法的类除了自恋还有什么用?而有些必须要被始化和释放等管理的 对象只有业务方法它也工作不起来,所以至少有两类功能要在同一类中完成。假如它们的重载方法足够多时,你会看到它们是很多功能的方法按字母排序混合在一 起。
典型的就是URLConnection类,你打开它时,设置请求参数的方法,获取响应头域的方法,获取输入输出的方法一大堆。不过还好是它们的命名方式使很多功能相同的方法能排在一起。
如何有效的组织一个实现的不同功能的方法的分类?我们可以通过多接口来完成。
这个思想早在COM时代就已经采用了,无论你获取的是IUnKnown接口还是IDispatch接口还是业务接口,其实反回的都是同一个 CoClass对象,但你获取不同接口看到的是不同功能的函数。同时你只要实例化一次,获取到任何接口就可以从这一接口生成其它接口,这对于调用者是非常 有意义的,我不仅能得到不同功能的函数分类,而且不需要多次实例化多个对象。
- interface IMath {
-
- public int add(int a, int b);
-
- public int mul(int a, int b);
- }
-
- interface IStrUtil {
-
- public String concat(String s1, String s2);
-
- public String Upper(String s1);
-
- }
-
- interface IObjManager {
-
- void init();
-
- void destory();
-
- }
-
- class MyModuleImp implements IMath, IStrUtil, IObjManager {
-
- public int add(int a, int b) {
- return a + b;
- }
-
- public int mul(int a, int b) {
- return a * b;
- }
-
- public String concat(String s1, String s2) {
- return checkedConcat(s1, s2);
- }
-
- public String Upper(String s1) {
- return checkedUpper(s1);
- }
-
- public void init() {
- System.out.println("init.........");
- }
-
- public void destory() {
- System.out.println("destory.........");
- }
-
- String checkedConcat(String s1, String s2) {
-
- if (s1 == null && s2 == null)
- return "null";
- if (s1 == null) s1 = "null";
- if (s2 == null) s2 = "null";
- return s1 + s2;
- }
-
- String checkedUpper(String s1) {
-
- if (s1 == null) return null;
- return s1.toUpperCase();
- }
- }
OK!
IObjManager om = new MyModuleImp(); //或getObjFromNet如果我们把生成实例的方法统一封装起来,以及对象的生命周期管理都规定在一个类似IUnknown的接口中,我们就可以 把多功能的不同类有一个统一的管理行为,就象远程EJB对象的实例化。
om. 这时我们看到的只是IObjManager接口的方法,我们可以init.destory.
当我们需要它的业务方法时:
IMath m = (IMath)om; //相当于QueryInterface
m. 这时我们看到的只是math相关的功能方法。
另外当我们想增加一个方法并保留原有方法时只需在新的接口中声明,并让实现类继承自这个接口(其实就是在implements关键字后面加上接口名称)。这样新旧接口可以并存使用,既不影响以前的代码,也不影响后来的人使用新接口:
- interface IMathEx{
-
- public int add(int a, int b);
-
- public int mul(int a, int b);
-
- public double sqrt(double a);
-
- }
-
-
-
- class MyModuleImp implements IMath,IMathEx, IStrUtil, IObjManager{
-
- //增加public double sqrt(double a)的实现
-
- }
这样原来使用IMath来计算“加和乘“的代码根本不受影响,而新的程序可以使用IMathEx来计算“加,乘和开方”运算。
所以多接口不仅使我们能够有效的组织不同功能的代码,而且可以使对象具有统一的管理方法,同时避免多次生成对象带来的开销。