用《英雄聯盟》解釋一下面向對象中接口的作用_風聞
每天一个么么哒-2019-09-05 23:10
原創:陳如初哦。
在面向對象編程的思想中,接口是一個非常重要的概念。按書上介紹的,使用接口,可以實現運行時多態、易維護、易拓展等等優點。擁有多年編程經驗的人應該能理解這些話的含義,對於一個初學編程的萌新來説,看完這段話完全不知所云。那今天我用《英雄聯盟》為背景,詳細的分析一下接口在面向對象編程中的作用,以及使用接口的優勢。
這次使用java作為編寫demo的語言,主要原因有兩個:
java是最流行的編程語言,基本上學過編程的都會java語言;java是一門對面向對象特性支持比較好的語言;
還記得我剛開始學習java的時候,就很不理解接口的作用,感覺接口優點多餘。
例如我定義了一個接口,但是我在實現這個接口的類中還要寫接口的實現方法,那我不如直接就在這個類中寫實現方法豈不是更便捷,還省去了定義接口
相信不止我一個人有過這樣的疑惑吧。
後來隨着寫代碼,看閲讀別人的代碼,逐漸開始理解接口的作用了,慢慢覺得接口是一個非常方便和牛逼的東西。
教材上,網上解釋接口的例子大多數使用定義一個Animal接口,然後Dog實現了這個接口,Cat實現了這個接口;還有一種用USB接口舉例。大多數人看完還是一臉懵逼。
現在用一種新的方式——《英雄聯盟》為背景介紹一下。
説了這麼半天,開始進入正題吧。
先圈兩個重點:
Java之所以要有接口,是因為java不支持多繼承,使用接口,可以間接的實現多繼承的一些特性;像C++就不存在接口這個東西,因為C++支持多繼承在面向對象的概念中,子類(派生類)可以自動轉換為父類(基類)類型;也就是説,A類實現了接口B,那麼A的實例化對象可以自動轉換為B類型public class Main { public static void main(String[] args) { B a = new A(); }}interface B {}class A implements B {}
這樣的代碼是正確的。
開始demo部分,我們定義一個Skill接口,裏面有 Q、W、E、R 四個方法,代表英雄的四個技能。為了簡單,被動技能和召喚師技能就不寫了。
然後從五個位置上單、打野、中單、ADC、輔助中各挑選一個英雄,作為例子。上路中我最喜歡的是鋭雯,打野我玩的最多,糾結了半天選了盲僧。中單裏必須選亞索,ADC裏選擇了暴走蘿莉,輔助裏選擇了錘石。
先上代碼再解釋:
//技能接口
interface Skill {
void Q();
void W();
void E();
void R();
}
//放逐之刃-鋭雯
class RuiWen implements Skill {
public RuiWen() {
System.out.println(“斷劍重鑄之日,騎士歸來之時”);
}
@Override
public void Q() {
System.out.println(“折翼之舞”);
}
@Override
public void W() {
System.out.println(“震魂怒吼”);
}
@Override
public void E() {
System.out.println(“勇往直前”);
}
@Override
public void R() {
System.out.println(“放逐之鋒”);
}
}
//盲僧-李青
class LiQing implements Skill {
public LiQing() {
System.out.println(“我用雙手成就你的夢想”);
}
@Override
public void Q() {
System.out.println(“天音波/迴音擊”);
}
@Override
public void W() {
System.out.println(“金鐘罩/鐵布衫”);
}
@Override
public void E() {
System.out.println(“天雷破/摧筋斷骨”);
}
@Override
public void R() {
System.out.println(“猛龍擺尾”);
}
}
//疾風劍豪-亞索
class YaSuo implements Skill {
public YaSuo() {
System.out.println(“死亡如風,常伴吾生”);
}
@Override
public void Q() {
System.out.println(“斬鋼閃”);
}
@Override
public void W() {
System.out.println(“風之障壁”);
}
@Override
public void E() {
System.out.println(“踏前斬”);
}
@Override
public void R() {
System.out.println(“狂風絕息斬”);
}
}
//暴走蘿莉-金克斯
class JinKeSi implements Skill {
public JinKeSi() {
System.out.println(“規則就是用來打破的”);
}
@Override
public void Q() {
System.out.println(“槍炮交響曲!”);
}
@Override
public void W() {
System.out.println(“震盪電磁波!”);
}
@Override
public void E() {
System.out.println(“嚼火者手雷!”);
}
@Override
public void R() {
System.out.println(“超究極死神飛彈!”);
}
}
//魂鎖典獄長-錘石
class ChiShi implements Skill {
public ChiShi() {
System.out.println(“我們要怎樣進行這令人愉悦的折磨呢”);
}
@Override
public void Q() {
System.out.println(“死亡判決”);
}
@Override
public void W() {
System.out.println(“魂引之燈”);
}
@Override
public void E() {
System.out.println(“厄運鐘擺”);
}
@Override
public void R() {
System.out.println(“幽冥監牢”);
}
}
代碼有點多,但是很簡單,寫了5類,對應5個英雄。每個類的構造方法中,打印了這個英雄在排位中被選中時的台詞。每個類都實現了skill這個接口,並重寫了QWER這4個方法,在方法中打印了這個英雄技能的名稱。
在main方法中初始化這5個英雄,並調用每個英雄的QWER這四個技能,代碼:
public class Main { public static void main(String[] args) { //初始化鋭雯釋,放技能 Skill ruiWen = new RuiWen(); ruiWen.Q(); ruiWen.W(); ruiWen.E(); ruiWen.R(); //初始化李青,釋放技能 Skill liQing = new LiQing(); liQing.Q(); liQing.W(); liQing.E(); liQing.R(); //初始化亞索,釋放技能 Skill yaSuo = new YaSuo(); yaSuo.Q(); yaSuo.W(); yaSuo.E(); yaSuo.R(); //初始化金克斯,釋放技能 Skill jinKeSi = new JinKeSi(); jinKeSi.Q(); jinKeSi.W(); jinKeSi.E(); jinKeSi.R(); //初始化錘石,釋放技能 Skill chuiShi = new ChiShi(); chuiShi.Q(); chuiShi.W(); chuiShi.E(); chuiShi.R(); }}
注意一點:
我們在實例化這5個英雄時,這5個英雄都是Skill類型的
看一下運行結果:
可以看到,這5個英雄依次被實例化,並釋放了QWER這4個技能。
可能到這有的同學沒看懂,這和接口有什麼關係?接口帶來了哪些好處?
簡單分析一下:
接口這個概念,其實就是定義了一種規範。在 Skill 這個接口中,定義了Q、W、E、R這四個方法,只要是實現了這個接口的類,一定會有這四個方法。接口可以看做是實現多繼承的一種方式(這樣説可能不嚴謹)。java中沒有多繼承這種機制,失去了一些靈活性。但是去掉多繼承後,語法簡單了很多,像C++中,因為有多繼承,又引入了虛繼承的概念。説多了,回到正題。一個類實現一個接口後,可以看做是這個接口的子類,所以,我們在實例化英雄時(new Ruiwen()等),可以直接實例化為 Skill 類型的。
結合這兩點,所以我們每一個Skill類型的對象,都可以調用 Q、W、E、R 這四個方法。
有人會提出疑問,我在每個類中都定義 Q、W、E、R 這四個方法不就行了。但是如何保證每個類裏都有這四個方法呢?通過接口約束,可以保證,所有實現這個接口的類中,一定有這四個方法。
再通過下面這個用法,看一下接口怎樣實現多態的:
import java.util.Scanner;public class Main { public static void main(String[] args) { Skill hero; Scanner scanner = new Scanner(System.in); switch (scanner.nextInt()) { case 1: hero = new RuiWen(); break; case 2: hero = new LiQing(); break; case 3: hero = new YaSuo(); break; case 4: hero = new JinKeSi(); break; case 5: hero = new ChiShi(); break; default: hero = new RuiWen(); } hero.Q(); hero.W(); hero.E(); hero.R(); }}
簡單看一下代碼,定義了一個Skill類型的變量hreo。通過輸入不同的值,來判斷實例化哪一個英雄。最後調用英雄的 Q、W、E、R 方法。
先輸入 1 看一下,輸入 1 應該是實例化鋭雯這個英雄
沒有問題,輸入1成功實例化了鋭雯這個英雄,並調用了鋭雯的四個技能。
再換一個輸入值看一下:
這次輸入了 2 ,實例化了李青這個英雄,並調用了李青的四個技能。簡單説一下使用了接口後的優勢:
使用接口後,實現了運行時多態,也就是 hero 具體是哪個類的對象,在編譯階段我們是不知道的,只有當程序運行時,通過我們輸入的值才能確定 hero 是哪個類的對象。使用了接口後,所有實現了 Skill 接口的類,都可以實例化為 Skill 類型的對象。如果不是這樣,那有多少個英雄(類)就要定義多少個變量。現在英雄聯盟有145個英雄,那就要定義145個變量,這。。。。
總結:
接口的作用是定義了定義了一些規範(也就是定義了一些方法),所有實現了這個接口的類,必須要遵守這些規範(類中一定有這些方法)一個類實現了一個接口, 可以看做 是這個接口的子類,注意是可以看做。子類類型可以自動轉換為父類類型,所以任何出現接口的地方,都可以使用實現這個接口的類的對象代替。最常見的就是方法中傳參,定義一個接口類型的變量,傳入一個實現了接口的對象。