用《英雄联盟》解释一下面向对象中接口的作用

在面向对象编程的思想中,接口是一个非常重要的概念。按书上介绍的,使用接口,可以实现运行时多态、易维护、易拓展等等优点。拥有多年编程经验的人应该能理解这些话的含义,对于一个初学编程的萌新来说,看完这段话完全不知所云。那今天我用《英雄联盟》为背景,详细的分析一下接口在面向对象编程中的作用,以及使用接口的优势。

这次使用java作为编写demo的语言,主要原因有两个:

  1. java是最流行的编程语言,基本上学过编程的都会java语言;
  2. java是一门对面向对象特性支持比较好的语言;

还记得我刚开始学习java的时候,就很不理解接口的作用,感觉接口有点多余。

例如我定义了一个接口,但是我在实现这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂不是更便捷,还省去了定义接口

相信不止我一个人有过这样的疑惑吧。 后来随着写代码,看阅读别人的代码,逐渐开始理解接口的作用了,慢慢觉得接口是一个非常方便和牛逼的东西。 教材上,网上解释接口的例子大多数使用定义一个Animal接口,然后Dog实现了这个接口,Cat实现了这个接口;还有一种用USB接口举例。大多数人看完还是一脸懵逼。 现在用一种新的方式——《英雄联盟》为背景介绍一下。 说了这么半天,开始进入正题吧。

先圈两个重点:

  1. Java之所以要有接口,是因为java不支持多继承,使用接口,可以间接的实现多继承的一些特性;像C++就不存在接口这个东西,因为C++支持多继承
  2. 在面向对象的概念中,子类(派生类)可以自动转换为父类(基类)类型;也就是说,A类实现了接口B,那么A的实例化对象可以自动转换为B类型
 1public class Main {
 2    public static void main(String[] args) {
 3        B a = new A();
 4    }
 5}
 6
 7interface B {
 8
 9}
10
11class A implements B {
12
13}

这样的代码是正确的。


开始demo部分,我们定义一个Skill接口,里面有 QWER 四个方法,代表英雄的四个技能。为了简单,被动技能和召唤师技能就不写了。 然后从五个位置上单、打野、中单、ADC、辅助中各挑选一个英雄,作为例子。上路中我最喜欢的是锐雯,打野我玩的最多,纠结了半天选了盲僧。中单里必须选亚索,ADC里选择了暴走萝莉,辅助里选择了锤石。

先上代码再解释:

  1//技能接口
  2interface Skill {
  3    void Q();
  4
  5    void W();
  6
  7    void E();
  8
  9    void R();
 10}
 11
 12//放逐之刃-锐雯
 13class RuiWen implements Skill {
 14
 15    public RuiWen() {
 16        System.out.println("断剑重铸之日,骑士归来之时");
 17    }
 18
 19    @Override
 20    public void Q() {
 21        System.out.println("折翼之舞");
 22    }
 23
 24    @Override
 25    public void W() {
 26        System.out.println("震魂怒吼");
 27    }
 28
 29    @Override
 30    public void E() {
 31        System.out.println("勇往直前");
 32    }
 33
 34    @Override
 35    public void R() {
 36        System.out.println("放逐之锋");
 37    }
 38}
 39
 40//盲僧-李青
 41class LiQing implements Skill {
 42
 43    public LiQing() {
 44        System.out.println("我用双手成就你的梦想");
 45    }
 46
 47    @Override
 48    public void Q() {
 49        System.out.println("天音波/回音击");
 50    }
 51
 52    @Override
 53    public void W() {
 54        System.out.println("金钟罩/铁布衫");
 55    }
 56
 57    @Override
 58    public void E() {
 59        System.out.println("天雷破/摧筋断骨");
 60    }
 61
 62    @Override
 63    public void R() {
 64        System.out.println("猛龙摆尾");
 65    }
 66}
 67
 68//疾风剑豪-亚索
 69class YaSuo implements Skill {
 70
 71    public YaSuo() {
 72        System.out.println("死亡如风,常伴吾生");
 73    }
 74
 75    @Override
 76    public void Q() {
 77        System.out.println("斩钢闪");
 78    }
 79
 80    @Override
 81    public void W() {
 82        System.out.println("风之障壁");
 83    }
 84
 85    @Override
 86    public void E() {
 87        System.out.println("踏前斩");
 88    }
 89
 90    @Override
 91    public void R() {
 92        System.out.println("狂风绝息斩");
 93    }
 94}
 95
 96//暴走萝莉-金克斯
 97class JinKeSi implements Skill {
 98
 99    public JinKeSi() {
100        System.out.println("规则就是用来打破的");
101    }
102
103    @Override
104    public void Q() {
105        System.out.println("枪炮交响曲!");
106    }
107
108    @Override
109    public void W() {
110        System.out.println("震荡电磁波!");
111    }
112
113    @Override
114    public void E() {
115        System.out.println("嚼火者手雷!");
116    }
117
118    @Override
119    public void R() {
120        System.out.println("超究极死神飞弹!");
121    }
122}
123
124//魂锁典狱长-锤石
125class ChuiShi implements Skill {
126
127    public ChiShi() {
128        System.out.println("我们要怎样进行这令人愉悦的折磨呢");
129    }
130
131    @Override
132    public void Q() {
133        System.out.println("死亡判决");
134    }
135
136    @Override
137    public void W() {
138        System.out.println("魂引之灯");
139    }
140
141    @Override
142    public void E() {
143        System.out.println("厄运钟摆");
144    }
145
146    @Override
147    public void R() {
148        System.out.println("幽冥监牢");
149    }
150}

代码有点多,但是很简单,写了5类,对应5个英雄。每个类的构造方法中,打印了这个英雄在排位中被选中时的台词。每个类都实现了skill这个接口,并重写了QWER这4个方法,在方法中打印了这个英雄技能的名称。 在main方法中初始化这5个英雄,并调用每个英雄的QWER这四个技能,代码:

 1public class Main {
 2    public static void main(String[] args) {
 3        //初始化锐雯释,放技能
 4        Skill ruiWen = new RuiWen();
 5        ruiWen.Q();
 6        ruiWen.W();
 7        ruiWen.E();
 8        ruiWen.R();
 9
10        //初始化李青,释放技能
11        Skill liQing = new LiQing();
12        liQing.Q();
13        liQing.W();
14        liQing.E();
15        liQing.R();
16
17        //初始化亚索,释放技能
18        Skill yaSuo = new YaSuo();
19        yaSuo.Q();
20        yaSuo.W();
21        yaSuo.E();
22        yaSuo.R();
23
24        //初始化金克斯,释放技能
25        Skill jinKeSi = new JinKeSi();
26        jinKeSi.Q();
27        jinKeSi.W();
28        jinKeSi.E();
29        jinKeSi.R();
30
31        //初始化锤石,释放技能
32        Skill chuiShi = new ChuiShi();
33        chuiShi.Q();
34        chuiShi.W();
35        chuiShi.E();
36        chuiShi.R();
37    }
38}

注意一点:

我们在实例化这5个英雄时,这5个英雄都是Skill类型的

看一下运行结果:

可以看到,这5个英雄依次被实例化,并释放了QWER这4个技能。

可能到这有的同学没看懂,这和接口有什么关系?接口带来了哪些好处?

简单分析一下:

  1. 接口这个概念,其实就是定义了一种规范。在 Skill 这个接口中,定义了Q、W、E、R这四个方法,只要是实现了这个接口的类,一定会有这四个方法。
  2. 接口可以看做是实现多继承的一种方式(这样说可能不严谨)。java中没有多继承这种机制,失去了一些灵活性。但是去掉多继承后,语法简单了很多,像C++中,因为有多继承,又引入了虚继承的概念。说多了,回到正题。一个类实现一个接口后,可以看做是这个接口的子类,所以,我们在实例化英雄时(new Ruiwen()等),可以直接实例化为 Skill 类型的。

结合这两点,所以我们每一个Skill类型的对象,都可以调用 Q、W、E、R 这四个方法。 有人会提出疑问,我在每个类中都定义 Q、W、E、R 这四个方法不就行了。但是如何保证每个类里都有这四个方法呢?通过接口约束,可以保证,所有实现这个接口的类中,一定有这四个方法。

再通过下面这个用法,看一下接口怎样实现多态的:

 1import java.util.Scanner;
 2public class Main {
 3    public static void main(String[] args) {
 4        Skill hero;
 5        Scanner scanner = new Scanner(System.in);
 6        switch (scanner.nextInt()) {
 7            case 1:
 8                hero = new RuiWen();
 9                break;
10            case 2:
11                hero = new LiQing();
12                break;
13            case 3:
14                hero = new YaSuo();
15                break;
16            case 4:
17                hero = new JinKeSi();
18                break;
19            case 5:
20                hero = new ChuiShi();
21                break;
22            default:
23                hero = new RuiWen();
24        }
25
26        hero.Q();
27        hero.W();
28        hero.E();
29        hero.R();
30    }
31}

简单看一下代码,定义了一个Skill类型的变量hreo。通过输入不同的值,来判断实例化哪一个英雄。最后调用英雄的 Q、W、E、R 方法。

先输入 1 看一下,输入 1 应该是实例化锐雯这个英雄

没有问题,输入1成功实例化了锐雯这个英雄,并调用了锐雯的四个技能。

再换一个输入值看一下:

这次输入了 2 ,实例化了李青这个英雄,并调用了李青的四个技能。

使用了接口后的优势:

  1. 使用接口后,实现了运行时多态,也就是 hero 具体是哪个类的对象,在编译阶段我们是不知道的,只有当程序运行时,通过我们输入的值才能确定 hero 是哪个类的对象。

  2. 使用了接口后,所有实现了 Skill 接口的类,都可以实例化为 Skill 类型的对象。如果不是这样,那有多少个英雄(类)就要定义多少个变量。现在英雄联盟有145个英雄,那就要定义145个变量,这。。。。

总结:

  1. 接口的作用是定义了定义了一些规范(也就是定义了一些方法),所有实现了这个接口的类,必须要遵守这些规范(类中一定有这些方法)
  2. 一个类实现了一个接口, 可以看做 是这个接口的子类,注意是可以看做。子类类型可以自动转换为父类类型,所以任何出现接口的地方,都可以使用实现这个接口的类的对象代替。最常见的就是方法中传参,定义一个接口类型的变量,传入一个实现了接口的对象。

最后:文章是下班后半夜写的,加上自身能力有限,文中如有不正确的地方,欢迎评论区探讨,共同提高。

发表了56篇文章 · 总计128.44k字
本博客已稳定运行
© QX
使用 Hugo 构建
主题 StackJimmy 设计