首页 > 代码库 > Java编程思想(六) —— 接口
Java编程思想(六) —— 接口
接口一章讲到抽象类和接口,单纯看书上的抽象类感觉很抽象。而且接口这一章的知识点,并没有像之前的讲的详细,而且碎且难,有几个问题是几个人在研讨会提出的,之前多态的有一个域的问题也是研讨会有人提出,那个相对容易理解一些。
1)抽象类
class TV{ public void show(){ System.out.println("TV"); } } class LeTV extends TV{ public void show(){ System.out.println("LeTV"); } } class MiTV extends TV{ public void show(){ System.out.println("MiTV"); } } class SanTV extends TV{ public void show(){ System.out.println("SanTV"); } } public class EveryTV { public static void tvshow(LeTV tv){ tv.show(); } public static void tvshow(MiTV tv){ tv.show(); } public static void tvshow(SanTV tv){ tv.show(); } public static void main(String[] args) { tvshow(new LeTV()); tvshow(new MiTV()); tvshow(new SanTV()); } }
其实会发现,从TV一开始,并没有创建TV对象,因为TV对象没什么意义。
java提供了一种抽象方法的机制,C++中叫纯虚函数。
包含抽象方法的类为抽象类,如果一个类有一个或多个的抽象方法,那么类一定要定义为抽象类。
抽象的方法声明可以没有方法体。如下:
abstract show();
抽象类不可new,确保抽象类的纯粹性,这就是抽象类,其实这也是抽象类存在的一个原因。
上述的TV类可以改写成抽象类:
abstract class TV{ abstract void show(); }
注意,子类同样需要继承方法,而且要有方法体。
其实电视本来就很抽象,TV本身不需要具体的show方法,具体的电视有具体的实现方法,new TV这个类也没什么用,TV就是一个抽象的概念。
看了一篇英文的回答——What is an abstract class, and when should it be used?
讲的是动物吃东西,动物本身也是一个抽象层次上的概念,但是动物有吃东西的方法,每种不同的动物有自己的吃法,吃肉,吃草,但是总得吃,动物这个概念性的东西,就可以声明为抽象类,如果是普通类,那么子类不重写也可以,那么默认就变成了和动物一样的吃法,但是动物本身并没有吃法,定义为抽象类,抽象方法在子类中需要有实现,这样扩展性也好,这应该就是设计者的初衷。
2)接口
public interface TV {}不定义public的话,接口只具有包内的访问权限,而接口的方法不声明为public,也是public的,接口可以有域,隐式为static和final。
当然,我们完全可以将TV设计为一个接口。那么为什么有抽象类还要有接口呢?
接口有一个特定的地方,实现接口的类必须实现接口的所有方法,抽象类除外,抽象类可以选择性的实现。这个问题后面总结时解答。
3)完全解耦
耦合性是软件工程的一个概念,程序设计讲究高内聚低耦合,耦合性可以简单看成依赖性,就是粘合度过紧。
书先举了一个例子。
class TV{ public String name(){ return getClass().getSimpleName();//拿到类名 } Object show(Object input){ return input; } } class LeTV extends TV{ String show(Object input){ return (String)input+" in letv"; } } class MiTV extends TV{ String show(Object input){ return (String)input+" in mitv"; } } public class TVShow { public static void play(TV tv , Object o){ System.out.println(tv.name()); System.out.println(tv.show(o)); } public static void main(String[] args) { TVShow ts = new TVShow(); String mylove = "my love tv show"; ts.play(new LeTV(), mylove); ts.play(new MiTV(), mylove); } }
TVShow的play方法可以根据接收的具体的TV参数而调用不同的方法,这种模式称为—— 策略模式。
接下来有Computer,但是专门处理Game。
class Game{ private static int count; private final int id = count++; public String toString(){ return "Game"+id; } } public class Computer{ public String name(){ return getClass().getSimpleName();//拿到类名 } Game show(Game input){ return input; } } class LeComputer extends Computer{ Game show(Game input){ return input; } } class MiComputer extends Computer{ Game show(Game input){ return input; } }
虽然书上举的是filter和processor的例子,但是书上这里有一点错误,虽然Computer和TV具有相同的接口元素,但是Computer不是继承TV,根本就是两个类,所以Computer用在TVShow的play方法正常运行肯定是错的。有这个例子也可以看出,TVShow的方法和TV耦合过紧了,只支持TV,要是智能电视呢?
所以,将TV设计为一个接口:
public interface TV { String name(); Object show(Object input); } public class TVShow { public static void play(TV tv , Object o){ System.out.println(tv.name()); System.out.println(tv.show(o)); } } public abstract class AbstractTV implements TV{ public String name() { return getClass().getSimpleName(); } public abstract String show(Object input); public static void main(String[] args) { TVShow ts = new TVShow(); String mylove = "my love tv show"; ts.play(new LeTV(), mylove); ts.play(new MiTV(), mylove); } } class LeTV extends AbstractTV{ public String show(Object input){ return (String)input+" in letv"; } } class MiTV extends AbstractTV{ public String show(Object input){ return (String)input+" in mitv"; } }
是不是觉得很麻烦,还要我们自己新建一个AbstractTV去实现TV接口。有什么用呢?看看Game的改变:
class Game{ private static int count; private final int id = count++; public String toString(){ return "Game"+id; } } class ComputerAdapter implements TV{ private Computer c ; ComputerAdapter(Computer c){ this.c = c; } public String name() { return c.name(); } public Game show(Object input) { return c.show((Game)input); } } public class ComputerShow{ public String name(){ return getClass().getSimpleName();//拿到类名 } Game show(Game input){ return input; } public static void main(String[] args) { Game g = new Game(); TVShow.play(new ComputerAdapter(new LeComputer()),g); TVShow.play(new ComputerAdapter(new MiComputer()),g); } } class LeComputer extends Computer{ Game show(Game input){ return input; } } class MiComputer extends Computer{ Game show(Game input){ return input; } } class Computer{ public String name(){ return getClass().getSimpleName();//拿到类名 } Game show(Game input){ return input; } }
发现没有,原来不能给Computer使用的TVShow的play方法现在可以用了,原因是ComputerAdapter实现了TV接口。
其实这种模式又是另外一种设计模式——适配器模式。ComputerAdapter接收不同的Computer同时实现TV接口,以便后面用于TVShow方法,不然单纯的Computer对象无法作为方法参数,适配器能将你所拥有的接口去产生所需要的接口,即这个例子的接口传入,其实很类似策略模式。ComputerAdapter本身就是代理,你只需要传入Computer对象,而看不到里面的方法实现。
还有一个有趣的地方,虽说是实现接口的所有方法,但是ComputerAdapter和AbstractTV实现接口的show方法时却与不同的类型,原因在Object上,如果TV接口的show方法不是Object类型而是某一具体的对象的话,那么实现方法的时候方法类型就要一致。
这种接口的实现用处就是降低耦合性,TVShow不仅能传入继承AbstractTV的对象,对于其他实现TV接口的对象也是可以使用的。刚开始看可能有点看懂,我看书也看了好久,一大堆东西,但总算搞明白了。
4)多重继承
由于只能继承一个类,但是可以实现多个接口,便拥有多个接口的功能。
public class A extends B implements C,D,E{}
5)接口的域
放在接口的域都是自动为static和final,同时也是public的。其实特性反过来证明也是可以的。练习题也有。
public interface T { int A=1; } public class Test implements T{ public static void main(String[] args) { System.out.println(Test.A); //System.out.println(Test.A++); } }
静态域才可以通过类名直接访问,final的域不能改变,在java5开始,就有了枚举类型了。
最后,就像书上说的“确定接口是理想选择,因而应该总是选择接口而不是具体的类。”这其实是引诱。
通篇下来,会发现接口真的很抽象,抽象要应需求而用,而不是为了用了用。
这和设计模式是一样的道理。
Java编程思想(六) —— 接口