2016/5/10

interface介面:Java為例

接下來討論interface介面這東西,先假設我們要做一個海洋樂園遊戲,也假設我們不會用interface,也許你會先定義fish這類別且有swim():
public abstract class Fish {
    protected String name;
    public Fish(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public abstract void swim();
}
每種魚游泳方式可能不一樣,所以swim()為abstract,那class也要abstract了。



然後開始做其他魚,接著定義每個魚的游泳方式:
public class Anemonefish extends Fish {
   public Anemonefish(String name) {
       super(name);
   }
    @Override
    public void swim() {
        System.out.printf("小丑魚 %s 游泳%n", name);
    }
}
public class Sharkfish extends Fish{
    public Sharkfish(String name) {
       super(name);
   }
    @Override
    public void swim() {
        System.out.printf("鯊魚 %s 游泳%n", name);
    }
}
再來,如果我要加個人類進去呢?這樣做嗎?也一樣嗎?但這樣並不符合設計邏輯,讓人類也繼承Fish,不太對...
這時就要用interface介面定義行為:
public interface Swimmer {
    public abstract void swim();
}
現在你人類可以這樣做:
public class Human implements Swimmer {
    private String name;
    public Human(String name) {
        this.name = name;
    } 
    public String getName() {
        return name;
    }
    @Override
    public void swim() {
        System.out.printf("人類 %s 游泳%n", name);
    }
}
透過implements關鍵字,讓Human擁有Swimmer這個行為,並且實作swim(),如不實作前面要加abstract。
也讓Fish擁有這個行為,這樣程式會比較有彈性:
public abstract class Fish implements Swimmer {
    protected String name;
    public Fish(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    @Override
    public abstract void swim();
}

接下來再看一個我們上次在多型中看過的語法結構,只差這次是interface,也有這種結構
Swimmer swimmer1 = new Sharkfish("aaa");
Swimmer swimmer2 = new Human("bbb");
Swimmer swimmer3 = new Submarine("ccc");
在多型是用「一種」來解釋,這裡是用擁有某種行為來解釋:
可用Sharkfish是不是擁有Swimmer行為或是Human是不是實作Swimmer介面,而這三行都可以通過編譯,因他們確實都擁有也實作了介面與行為。

再看:
Swimmer swimmer = new Sharkfish();//通過編譯
Sharkfish shark = swimmer;//編譯失敗,因有swimmer行為的物件,不一定就是Sharkfish

我們可以加扮演語法():
Swimmer swimmer1 = new Sharkfish("123");
Sharkfish shark = (Sharkfish) swimmer1;//swimmer1就是Sharkfish的instance,扮演Sharkfish當然通過編譯了。

Swimmer swimmer = new Sharkfish("aaa");
Fish fish = swimmer;//編譯失敗,因為實作Swimmer介面的物件不一定就繼承Fish,像Human就沒繼承。

Swimmer swimmer = new Sharkfish("aaa");//成功
Fish fish = (Fish) swimmer;//編譯成功,你知道有Swimmer行為的物件,不一定繼承Fish,那讓swimmer當 (Fish)就成功了,而執行時swimmer也參考Sharkfish實例。

執行拋出ClassCastException錯誤
Swimmer swimmer = new Human("aaa");
Sharkfish shark = (Sharkfish) swimmer;
swimmer參考Human的實例,現在要它當鯊魚當然錯誤了。

執行拋出ClassCastException錯誤
Swimmer swimmer = new Submarine("aaa");
Fish fish = (Fish) swimmer;
swimmer 參考Submarine實例,現在讓swimmer扮演Fish,Submarine也沒繼承Fish,這樣當然錯了。

現在來讓魚游起來:
public class OceansGame {
    public static void main(String[] args) {
        Piranhafish piranhafish = new Piranhafish("aaa");
        Sharkfish sharkfish = new Sharkfish("bbb");
        Human human = new Human("ccc");
        doSwim(piranhafish);
        doSwim(sharkfish);
        doSwim(human);
    }
    public static void doSwim(Fish fish) {
        fish.swim();
    }
    public static void doSwim(Human human) {
        human.swim();
    }
}
因為Human沒繼承Fish所以要另外寫一個。 來思考一下,雖然有的沒繼承要另外寫,但是不是可以利用他們都同樣擁有同個行為來做呢?它們都擁有Swimmer行為的特徵,所以可以這樣寫:
public class OceansGame {
    public static void main(String[] args) {
        doSwim(new Anemonefish("尼莫"));
        doSwim(new Sharkfish("蘭尼"));
        doSwim(new Human("賈斯汀"));
        doSwim(new Submarine("黃色一號"));
    }
    public static void doSwim(Swimmer swimmer) {
        swimmer.swim();
    }
}

那...,現在有個海上飛機具有飛行的行為:
public interface Flyer {
    public abstract void fly();
}

也要能再水上航行:
public class Seaplane implements Swimmer, Flyer {
    private String name;
    
    public Seaplane(String name) {
        this.name = name;
    }
    
    @Override
    public void fly() {
        System.out.printf("海上飛機 %s 在飛%n", name);
    }

    @Override
    public void swim() {
        System.out.printf("海上飛機 %s 航行海面%n", name);
    }
}
Java中,一個類別可實作多個介面。

那如果是飛魚呢?
public class FlyingFish extends Fish implements Flyer {
    public FlyingFish(String name) {
        super(name);
    }
    
    @Override
    public void swim() {
        System.out.println("飛魚游泳");
    }

    @Override
    public void fly() {
        System.out.println("飛魚會飛");
    } 
}
同時繼承某個類別,也實作某個介面。例如FlyingFish是一種魚,也擁有Flyer的行為。
現在讓他們動起來:
public class OceansGame {
    public static void main(String[] args) {
        doSwim(new Seaplane("空軍零號"));
        doSwim(new FlyingFish("甚平"));
    }
    public static void doSwim(Swimmer swimmer) {
        swimmer.swim();
    }
}
run:
海上飛機 空軍零號 航行海面
飛魚游泳

現在又有一個需求了!把潛水、游泳分開。
public interface IF_Diver extends Swimmer{
    public abstract void dive();
}
可以看到介面也可以繼承介面。
設一般的船可以在海面上航行,也就是擁有Swimmer行為:
public class Boat implements Swimmer{
    protected String name;
    public Boat(String name) {
        this.name = name;
    }
    @Override
    public void swim() {
        System.out.printf("船在水面 %s 航行%n", name);
    }
}

剛剛的潛水挺,可以潛水也可海上航行:
public class Submarine extends Boat implements IF_Diver{
    public Submarine(String name) {
        super(name);
    }
    @Override
    public void dive() {
        System.out.printf("潛水艇 %s 潛行%n", name);
    }
}
同時繼承類別和實作介面。

裡面的變數宣告:
interface Vehicle {
      //右轉最大角度
    public static final int MAX_TURN_ANGLE = 60;
    public static final int MAX_TURN_ANGLE = 50;
    public void turnRight();
  }
在interface中,也只能定義public static final的列舉常數,所以不寫public static final也可。

以下程式碼會編譯錯誤,因為interface Action裡的method預設public,而重新定義execute()的Some沒標public。
public class Main {
    public static void main(String[] args) {
        Action action = new Some();
        action.execute();
    }
}
interface Action {
    void execute();
}
class Some implements Action {
    void execute() {
        System.out.println("作一些服務");
    }
}

再提一個介面繼承的例子:
interface Some {
    void execute();
    void doSome();
}
interface Other {
    void execute();
    void doOther();
}
public class Service implements Some, Other {
    @Override
    public void execute() {
        System.out.println("execute()");
    }
    @Override
    public void doSome() {
        System.out.println("doSome()");
    }
    @Override
    public void doOther() {
        System.out.println("doOther()");
    }
}
如果Some和Other中的execute()一樣的話,我們就可以用介面繼承改寫:
interface Action {
    void execute();
}
interface Some extends Action {
    void doSome();
}
interface Other extends Action {
    void doOther();
}
public class Service implements Some, Other {
    @Override
    public void execute() {
        out.println("execute()");
    }
    @Override
    public void doSome() {
        out.println("doSome()");
    }
    @Override
    public void doOther() {
        out.println("doOther()");
    }
}


結論:

  • 一個class可同時實作多個interface,其實就是多重繼承。
  • 只定義有哪些行為,但不定義實作內容,所以interface裡method一定是public abstract開頭。就算不寫系統也會幫你寫好。
  • 如有一class實作一interface,1.可選擇實作它,也就是重新定義2.method標註abstract。
  • 也可以繼承介面。
  • 可讓程式更有彈性和維護性高。
  • 具有多型。
  • 只能宣告常數,不能宣告 instance variable。



參考資料:
良葛格-介面定義行為
良葛格-行為的多型
良葛格-解決需求變化
物件導向軟體工程-介面

沒有留言:

張貼留言