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. 不同類族的多個類需要實現同樣的方法時(接口)

接口還有許多沒有總結的特點,例如泛型接口,總之學會規範使用接口,就會在將來的項目中,有着很大的幫助。

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