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开始,就有了枚举类型了。


最后,就像书上说的“确定接口是理想选择,因而应该总是选择接口而不是具体的类。”这其实是引诱。

通篇下来,会发现接口真的很抽象,抽象要应需求而用,而不是为了用了用。

这和设计模式是一样的道理。






郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。