上一篇讲完了抽象类,这一篇主要讲解比抽象类更加抽象的内容——接口。
什么是接口呢?先来看一个现实中的栗子,我们常用的插座,一般分为两孔和三孔,所以基本上不管是什么电器,只要插头插进去就可以正常使用,想想看,如果没有这样的规范,有十几种不同的插座孔,每个电器的插头都不一样,还不得崩溃掉。
先来看个栗子:
/**
* @author Frank
* @create 2017/11/22
* @description 可比较接口,用于实现类对象排序
*/
public interface Isortable {
//a>b则返回正整数,相等则返回0,否则返回负整数
int compareWith(Object a,Object b);
}
这是一个简单的接口,使用interface关键字来定义接口。
接口是描述可属于任何类或者结构的一组行为,是用于定义程序的一种规范,任何实现了接口的类都必须实现接口的所有方法,体现的是“如果你是。。。就必须能。。。”的思想。
在接口中,不能有方法的实现,也就是说,里面所有方法都是public abstract的,里面只能由静态变量,不能存在普通成员变量,可谓是抽象的集大成者。那为什么要使用接口呢?
还是继续我们之前的比喻,任何按照规范进行生产的插头都能获得电力,而不同插座虽然生产工艺不同,质量也不一样,但并不影响电器的正常使用,电器并不会关心插座的内部实现,这样就能屏蔽掉一些底层的细节,只暴露出接口供电器使用。
对于软件开发而言,按照接口的规范进行调用,就能获得期望的功能,按照接口的规范实现接口的方法,就能提供所期望的功能。接口的意义在于抽象,而抽象可以很好的解耦合。
我们来回头看看上一篇的栗子,我们从具体的商品类中抽象出了一个商品类,从而实现了代码复用和统一管理,也降低了程序的耦合度,比如一个排序方法,参数设置为Phone类的话,那就只能往里面放Phone类型的对象,而如果设置成Goods类,则Phone、Television、Computer类的对象都可以传入进去,这样就降低了程序的耦合度,也就是相互之间的依赖度。
而接口则是更高层的抽象,主要是对于行为的抽象,可以把它看作是一组规范或者要求,就好比要开车就要先考驾照,这个驾照就相当于接口,你有了这个驾照,就代表你有开车的能力和资格,因为“如果你要有驾照,你就必须能开车“。这样交警就不会为难你了,你跟交警之间通过驾照这个接口进行交流和沟通,而不是用口才去说服他。想一想,如果没有驾照这种规范,总不能没见到一个开车的人都要先当场测试一下他的能力才能让他上路吧。
在复杂的系统构架中,往往分成很多层级,上层要使用下层提供的服务,而层级之间是通过接口进行交流的,上层对于下层仅仅是接口的依赖,而不是具体类的依赖,因为只要实现了相应的接口,就可以提供相应的服务,就像只要有教师资格证,就代表你有当老师的资格和本事,关注的不是你这个对象,而是你教书的能力,也就是你能提供的服务。当下层需要改变的时候,只要接口不变,上层可以完全不用改变,甚至可以在上层不知情的情况下完全换掉下层代码,正因为接口的存在,让层级之间的耦合度大大降低。就像你的U盘,如果旧的坏了,直接换上新的U盘就可以插上,即插即用,电脑跟U盘之间通过接口进行交流。
好了,说了这么多,真是很罗嗦,还是来看代码吧,实践出真知。我们来把上一篇的内容稍做修改,上面的代码定义了一个Isortable接口,用于比较两个对象,以用于之后的排序。
然后我们定义一个Sort类,用于进行排序,可以使用各种类型的排序,如冒泡排序,选择排序,快速排序,希尔排序,这里简单起见,只用了冒泡排序。
/**
* @author Frank
* @create 2017/11/22
* @description 排序类
*/
public class Sort {
//冒泡排序,从大到小排序
public static void bubbleSort(Isortable[] isortables){
for (int i = 0;i<isortables.length-1;i++){
for(int j = 0;j<isortables.length-i-1;j++){
if(isortables[j].sort(isortables[j+1])<0){
//交换两者的值
Isortable tmp = isortables[j+1];
isortables[j+1] = isortables[j];
isortables[j] = tmp;
}
}
}
}
}
然后在Goods类中实现Isortable接口。使用implements关键字。
/**
* @author Frank
* @create 2017/11/21
* @description
*/
public abstract class Goods implements Isortable{
//定义各个类共有的属性
private String title;
private Double price;
//定义构造器
public Goods(String title, Double price) {
this.title = title;
this.price = price;
}
//定义设置器和访问器
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
//声明抽象打印方法
abstract void print();
//实现Isortable接口,覆盖compareWith方法
@Override
public int compareWith(Object a) {
//由于还没有说明泛型,所以直接强制转换类型了
Goods g = (Goods)a;
if(this.price > g.getPrice()){
return 1;
}else if(this.price < g.getPrice()){
return -1;
}
return 0;
}
}
由于Goods类实现了Isortable接口,所以继承于Goods类的所有类也都实现了该接口,接下来我们修改一下测试类进行测试。
/**
* @author Frank
* @create 2017/11/21
* @description
*/
public class Test {
public static void main(String[] args) {
Goods[] goodsList = new Goods[3];
goodsList[0] = new Phone("IphoneX",9688.00,5.8,24.0);
goodsList[1] = new Computer("Alienware15C-R2738",17699.00,"i7-7700HQ","GTX1060");
goodsList[2] = new Television("SAMSUNG UA78KU6900JXXZ",21999.00,78.0,"4K");
Sort.bubbleSort(goodsList);
for (Goods g:goodsList)
System.out.println(g.getTitle()+" "+g.getPrice());
}
}
输出如下:
SAMSUNG UA78KU6900JXXZ 21999.0
Alienware15C-R2738 17699.0
IphoneX 9688.0
这样就按价格进行了从大到小的排序了,怎么样,接口用起来很简单方便吧,这样以后Goods类不管有多少子类,都可以用Sort的bubbleSort方法进行排序,还可以修改或者增加Sort类的方法,让它按你想要的规则进行排序。
其实在Java中已经有类似的接口了,Comparable接口和Comparator接口,因为使用了泛型,就不会像这里的代码需要强制类型转换了(而且强制类型转换也有一定风险),而很多方法可以对实现了Comparable接口的类进行排序,这就很棒了。(手动滑稽)
在实际使用中,往往每个人都会负责不同的模块开发,不同的模块之间通常用接口进行交流,因为如果A程序员开发A1类,需要使用B程序员开发的B1类,若是直接调用B1类,那就只能先等B1类开发好之后才能进行A1类的开发,而且如果B1类有任何改动,很可能需要修改A1类的代码,这样耦合度就很高了,而如果使用接口的话,A1类只需要接受接口类型的参数,就可以直接调用相应的方法,而B1类只需要实现接口即可,B1类即使有改动,只要接口不变,那么它向A1类提供的服务也不会变,这样A1类就不需要进行改变,耦合度就降低了。
现在小结一下:
接口是对一组特定行为的抽象,是一种规范,只能有方法签名,而不能有实现,所有的方法默认为abstract,且访问权限只能是public,不能有普通成员变量,可以有静态成员变量,接口可以继承接口,一个类可以实现一个接口,也可以实现多个接口,接口的存在可以降低程序的耦合度,增加程序的灵活性。引用大神的话便是,接口和实现分离,面向接口编程。
至此,接口讲解完毕,欢迎大家继续关注。
真正重要的东西,用眼睛是看不见的。
共同学习,写下你的评论
评论加载中...
作者其他优质文章