Java 接口(interface)的作用与好处

首先提出两个问题:

一、明明可以在类中直接写所需的方法,为什么还要多写一个接口(或抽象类)?
二、抽象类和接口都差不多,在什么时候才选择使用接口?

一、抽象类 为了多态的实现

假设现在有7个类,分别如下:
1. 动物(Animal)抽象类
2. 哺乳动物(Mammal)抽象类 继承动物类
3. 爬行动物(Reptile)抽象类 继承动物类
4. 老虎(Tiger) 继承爬行动物类
5. 蛇类(Snake) 继承爬行动物类
6. 兔子(Rabbit) 继承哺乳动物类
7. 农夫(Farmer)农夫可以喂养Animal

1. Animal类

动物都会行走,喝水,代码如下

abstract class Animal{
    public abstract void move();
    public abstract void drink();
}

2. Mammal类

abstract class Mammal extends Animal{
    //继承动物类的两个抽象方法,该类为抽象类,不用具体实现
}

3. Raptile类

abstract class Raptile extends Animal{
    //继承动物类的两个抽象方法,该类为抽象类,不用具体实现
}

4. Tiger类

public class Tiger extends Mammal{

    private static String name = "Tiger";  

    public String getName(){  
        return this.name;  
    }

    public void move(){
        System.out.println("Tiger moved to " + destination + ".");
    }

    public void drink(){
        System.out.println("Tiger down to drink water");
    }
}

5. Snake类

public class Snake extends Raptile{

    private static String name = "Snake";  

    public String getName(){  
        return this.name;  
    }

    public void move(){
        System.out.println("Snake moved to " + destination + ".");
    }

    public void drink(){
        System.out.println("Snake stretched his tongue to drink water");
    }
}

6. Rabbit类

public class Rabbit extends Mammal{

    private static String name = "Rabbit";  

    public String getName(){  
        return this.name;  
    }

    public void move(){
        System.out.println("Rabbit moved to " + destination + ".");
    }

    public void drink(){
        System.out.println("Rabbit put out it's tongue and drink.");
    }
}

7. Farmer类

农夫没有继承任何类,但农夫可以给动物喂水喝,而不关心给什么动物喂水喝,也不关心动物们从哪里来。

public class farmer{

    public void bringWater(String destination){
        System.out.println("Farmer bring water to " + destination +".");
    }

    public void feedWater(Animal animal, String destination){
        this.bringWater(destination);
        animal.move(destination);
        animal.drink();
    }

}

农夫依次去三个地方给三只动物喂水,执行Farmer喂水代码

public void f(){
    Tiger tiger = new Tiger();
    Snake snake = new Snake();
    Rabbit rabbit = new Rabbit();
    Farmer farmer = new Farmer();

    farmer.feedWater(tiger, room);
    farmer.feedWater(snake, grassland);
    farmer.feedWater(rabbit, kichen);
}

执行结果:

 [java] Farmer bring water to room.
 [java] Tiger moved to room.
 [java] Tiger down to drink water.
 [java] Farmer bring water to grassland.
 [java] Snake moved to grassland.
 [java] Snake stretched his tongue to drink water.
 [java] Farmer bring water to kichen.
 [java] Rabbit moved to kichen.
 [java] Rabbit put out it's tongue and drink.

如果老虎、蛇、兔子没有继承抽象类来重写同一个抽象方法,多态就不能实现
这样的话,农夫类就要根据参数类型重载多个feedwater()方法,像这样:

feedwater(Tiger tiger, String destination);
feedwater(Snake snake, String destination);
...

Tiger、Snake、Rabbit继承了Raptile、Mammer抽象类,而Raptile、Mammer类继承基类Animal抽象类,所以Tiger、Snake、Rabbit都向上转型为Animal类,例如可以把农夫喂水的执行代码写成下面这样:

public void f(){
    Animal tiger = new Tiger(); 
    Animal snake = new Snake();
    Animal rabbit = new Rabbit();
    Farmer farmer = new Farmer();

    farmer.feedWater(tiger, room);
    farmer.feedWater(snake, grassland);
    farmer.feedWater(rabbit, kichen);
}

既然抽象类与接口都能实现多态,那什么时候才需要使用接口呢?

二 、接口的使用

假设现在农夫学会了一个新方法,带动物让Tiger和Snake捕食,需要给Tiger、Snake加一个捕食方法hunt(),Rabbit则不需要此方法。
但从以上类中发现,Snake、Tiger继承于Raptile、Mammer抽象类,Mammer的子类中有Rabbit类,则hunt()方法不能直接写入Animal类中,因为写在Animal类中,Animal的所有方法将会直接继承到子类中,由于Rabbit类用不上hunt()方法,则会造成资源浪费。
现在考虑几种方案:
1. 直接将hunt()方法写在各肉食动物的类中
        若这样做,就不能实现多态,每个类中的hunt()方法只能由类对象进行调用,像这样:

Tiger tiger = new Tiger();
tiger.hunt(animal);
Snake snake = new Snake();
snake.hunt(animal);

        此时农夫类像这样,需要对Tiger、Snake类方法重载:

class Farmer{  
    public void bringWater(String destination){  
        System.out.println("Farmer bring water to " + destination + ".");  
    }  

    public void bringAnimal(Animal a,String destination){  
        System.out.println("Farmer bring " + a.getName() + " to " + destination + ".");  
    }  

    public void feedWater(Animal animal, String destination){
        this.bringWater(destination);
        animal.move(destination);
        animal.drink();
    }

    public void feedAnimal(Tiger tiger , Animal animal){  
        this.bringAnimal(animal,"Feeding Room");  
        tiger.move("Feeding Room"); 
        tiger.hunt(animal);
    }  
    public void feedAnimal(Snake snake, Animal animal){  
        snake.bringAnimal(animal,"Feeding Room");  
        snake.move("Feeding Room"); 
        snake.hunt(animal);
    }  

}  

        若有很多会捕食的动物,将需要大量重载,所以这个方案不可以取。
2. 增加 肉食动物 抽象类
        如果是加入肉食动物类与非肉食动物类,将会使得类族图复杂化,因为肉食动物中也有不会捕猎的动物。

这个时候就需要用到接口了。

1. Hunt接口

interface Hunt{
    public void hunt(Animal animal);
}

定义好了接口之后,直接由Tiger、Snake遵循这个接口,需要用到implements关键字:

2. Tiger类

public class Tiger extends Mammal implements Hunt{

    private static String name = "Tiger";  

    public String getName(){  
        return this.name;  
    }

    public void move(){
        System.out.println("Tiger moved to " + destination + ".");
    }

    public void drink(){
        System.out.println("Tiger down to drink water");
    }

    public void hunt(Animal animal){
        System.out.println("Tiger catched a " + animal.getName() + "and eated it." )
    }

}

3. Snake类

public class Snake extends Raptile implements Hunt{

    private static String name = "Snake";  

    public String getName(){  
        return this.name;  
    }

    public void move(){
        System.out.println("Snake moved to " + destination + ".");
    }

    public void drink(){
        System.out.println("Snake stretched his tongue to drink water");
    }

    public void hunt(Animal animal){
        System.out.println("Tiger catched a " + animal.getName() + "and eated it." )
    }

}

4. Farmer类

public class farmer{

    public void bringWater(String destination){  
        System.out.println("Farmer bring water to " + destination + ".");  
    }  

    public void bringAnimal(Animal a,String destination){  
        System.out.println("Farmer bring " + a.getName() + " to " + destination + ".");  
    }  

    public void feedWater(Animal animal, String destination){
        this.bringWater(destination);
        animal.move(destination);
        animal.drink();
    }

    public void feedAnimal(Hunt hunter, Animal animal){
        this.bringAnimal(animal,"Feeding Room");  
        Animal ht = (Animal)hunter;  
        ht.move("Feeding Room");  
        hunter.hunt(animal); 
    }
}

此时接口实现了多态。
接口也成为Java中的多重继承,在导出类中,如果是从一个非接口的类继承,那只能继承这一个类,其余的基元素都必须是接口,需要把所有的接口都置于implements关键字之后,用逗号将它们隔开。

现在总结一下文章一开始提出的两个问题

一、明明可以在类中直接写所需的方法,为什么还要多写一个接口(或抽象类)?
       1. 减少代码的书写(上边分析的代码重载)
       2. 提高了代码的可维护性和扩展性。
       3. 在团队合作中,代码的规范性

二、抽象类和接口都差不多,在什么时候才选择使用接口?
       1. 在当前类族中不是必须的(例如Tuger需要而Rabbit不需要)的方法时
       2. 不同类族的多个类需要实现同样的方法时(接口)

接口还有许多没有总结的特点,例如泛型接口,总之学会规范使用接口,就会在将来的项目中,有着很大的帮助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章