1 接口基本定义
当你可以灵活的使用抽象类和接口进行设计的时候,那么基本上就表示你面向对象的概念理解了,需要大量代码累积而成的。
抽象类与普通类相比,最大的优势在于可以实现子类覆写方法的控制,但是在抽象类中可能依然会保留一些普通方法,而普通方法中可能会涉及到一些安全或者隐私的操作问题,那么这样在进行开发过程中,如果要想对外部隐藏全部的实现细节,则就可以通过接口来进行描述。
接口可以理解为一个纯粹的抽象类(最原始的定义接口之中是只包含有抽象方法与全局常量的),从JDK 1.8开始,由于引入Lambda表达式的概念,所以接口的定义也得到了加强,除了抽象方法与全局常量之外,还可以定义普通方法或静态方法。如果从设计本身的角度来讲,接口之中的组成还是应该以抽象方法和全局常量为主。
在Java中,接口主要使用interface关键字来进行定义。
范例:定义一个接口
//由于类名称和接口名称的定义要求相同,所以为了区分接口,接口名称前往往会加上I。
interface IMessage{ //定义一个接口
public static final String INFO = "lks love hhy"; //全局常量
public abstract String getInfo(); //抽象方法
}
但是现在很明显的问题出现了,此时的接口肯定无法直接产生实例化对象,所以对接口的使用原则如下:
(1)接口需要被子类实现(implements),一个子类可以实现多个父接口;
(2)子类(如果不是抽象类),那么一定要覆写接口之中的全部抽象方法;
(3)接口对象可以利用子类对象的向上转型进行实例化。
范例:定义接口子类
interface IMessage{ //定义一个接口
public static final String INFO = "lks love hhy"; //全局常量
public abstract String getInfo(); //抽象方法
}
interface IChannel{
public abstract boolean connect();
}
class MessageImpl implements IMessage, IChannel{ //定义接口子类
public String getInfo(){
if( this.connect() ){
return "hello";
}
return "failed";
}
public boolean connect(){
System.out.println("connect!");
return true;
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage message = new MessageImpl();
System.out.println(message.getInfo());
}
}
以上是接口的基本使用,但是在Java里面使用接口的主要目的是一个子类可以实现多个接口,利用接口可以实现多继承的概念。
此时MessageImpl子类的对象可以任意的实现父接口的转换。
范例:观察转换
public class JavaDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl();
IChannel chan = (IChannel) msg;
System.out.println(chan.connect());
}
}
由于MessageImpl实现了IMessage和IChannel两个接口,所以这个子类可以是这两个接口任意一个接口的实例,那么就表示此时这两个接口实例之间是可以转换的。
在Java程序里,接口是不允许继承父类的,所以接口绝对不会是Object的子类,但是根据之前的分析可以发现,MessageImpl类是Object的子类,所以接口一定可以通过Object接受。
范例:观察Object与接口转换
public class JavaDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl();
Object obj = msg;
IChannel chan = (IChannel) obj;
System.out.println(chan.connect());
}
}
Object类对象可以接收所有数据类型,包括基本数据类型、类对象、接口对象、数组。
由于接口描述的是一个公共的定义标准,所以在接口之中所有的抽象方法的访问权限都为public,所以说写于不写都是一样的,例如,下面两个接口的本质是完全相同的:
方法不写访问权限也是public,不是default,所以覆写的时候只能够使用public。
虽然接口无法去继承一个父类,但是一个接口却可以通过extends继承若干个父接口,此时称为接口多继承。
范例:接口多继承
interface IMessage{ //定义一个接口
public static final String INFO = "lks love hhy"; //全局常量
public abstract String getInfo(); //抽象方法
}
interface IChannel{
public abstract boolean connect();
}
interface Temp extends IMessage, IChannel{
public abstract void print();
}
class MessageImpl implements Temp{ //定义接口子类
public void print(){}
public String getInfo(){
if( this.connect() ){
return "hello";
}
return "failed";
}
public boolean connect(){
System.out.println("connect!");
return true;
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl();
System.out.println(msg.getInfo());
}
}
在实际开发之中,接口的使用往往有三种形式:
(1)进行标准设置;
(2)表示一种操作的能力;
(3)暴露远程方法视图,这个一般都在RPC分布式开发中使用。
2 接口定义加强
接口最早的主要特点是全部由抽象方法和全局常量所组成,但是如果你的项目设计不当,就有可能出现一种严重的问题——不当接口设计,当接口被大量子类实现,此时需要增加一个新的接口方法,且对每个子类的实现相同,此时你就需要大量覆写,工作量繁重。这时,为了方便子类修改,往往不会让子类直接实现接口,而是中间追加一个过渡的抽象类。
interface IMessage{ //定义一个接口
public static final String INFO = "lks love hhy"; //全局常量
public abstract String getInfo(); //抽象方法
}
interface IChannel{
public abstract boolean connect();
}
interface Temp extends IMessage, IChannel{
public abstract void print();
}
abstract class Transition implements Temp{
}
class MessageImpl extends Transition{ //定义接口子类
public void print(){}
public String getInfo(){
if( this.connect() ){
return "hello";
}
return "failed";
}
public boolean connect(){
System.out.println("connect!");
return true;
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage msg = new MessageImpl();
System.out.println(msg.getInfo());
}
}
但是从JDK 1.8之后开始,为了解决接口设计的缺陷,所以接口之中允许开发者定义普通方法。接口中的普通方法必须加上default声明,但是该操作属于挽救功能,但是非必须的情况下不应该作为你设计的首选。
interface IMessage{ //定义一个接口
public static final String INFO = "lks love hhy"; //全局常量
public abstract String getInfo(); //抽象方法
public default void print(){
System.out.println("lks");
}
public static IMessage getInstance(){
return new MessageImpl();
}
}
class MessageImpl implements IMessage{ //定义接口子类
public String getInfo(){
return "Hello!";
}
}
public class JavaDemo{
public static void main(String[] args){
IMessage msg = IMessage.getInstance();
System.out.println(msg.getInfo());
msg.print();
}
}
除了可以追加普通方法之外,接口里面也可以定义static方法了,而static方法就可以通过接口直接调用。
如果现在真的可以在接口里面定义普通方法或static方法,那么这个功能就已经可以取代抽象类了,但是不应该将这两个组成作为接口的主要设计原则,而是仍应该奉行接口就是抽象方法。
3 使用接口定义标准
对于接口而言在开发之中最为重要的应用就是进行标准的制定,实际上在日常生活之中也会听见许多关于接口的名词,例如:USB接口、PCI接口、鼠标接口等等,那么这些实际上都是属于标准的应用。
interface IUSB{
public abstract boolean check();
public abstract String work();
}
class Computer{
public void plugin(IUSB usb){
if(usb.check()){
System.out.println(usb.work());
}else{
System.out.println("设备异常");
}
}
}
class Keyboard implements IUSB{
public boolean check(){
return true;
}
public String work(){
return "打字";
}
}
class Print implements IUSB{
public boolean check(){
return false;
}
public String work(){
return "打印";
}
}
public class JavaDemo{
public static void main(String[] args){
Computer computer = new Computer();
computer.plugin(new Keyboard());
computer.plugin(new Print());
}
}
4 工厂设计模式(Factory)
对于与接口而言,已经可以明确的清楚,必须有子类,并且子类可以通过对象的向上转型来获取接口的实例化对象,但是进行对象实例化的过程中,也可能存在有设计的问题。
范例:观察如下程序
interface IFood{
public abstract void eat();
}
class Apple implements IFood{
public void eat(){
System.out.println("eat apple");
}
}
public class JavaDemo{
public static void main(String[] args){
IFood food = new Apple();
food.eat();
}
}
在本程序之中根据接口进行子类的定义,并且利用对象向上转型进行接口实例化处理。
客户端需要明确的知道具体的哪一个子类。
范例:扩展一类食物
interface IFood{
public abstract void eat();
}
class Apple implements IFood{
public void eat(){
System.out.println("eat apple");
}
}
class Milk implements IFood{
public void eat(){
System.out.println("Drink milk");
}
}
public class JavaDemo{
public static void main(String[] args){
IFood food = new Milk();
food.eat();
}
}
此时的程序就表示出现有耦合的问题,而造成耦合最直接的元凶:“关键字new”。以JVM的设计为例,java实现可移植性的关键在于JVM,而JVM的核心原理在于利用一个虚拟来运行Java程序,所有的程序并不与具体的操作系统有任何的关联,而是由JVM来进行匹配,所以就得出结论:良好的设计应该避免耦合。
范例:工厂设计实现
interface IFood{
public abstract void eat();
}
class Apple implements IFood{
public void eat(){
System.out.println("eat apple");
}
}
class Milk implements IFood{
public void eat(){
System.out.println("Drink milk");
}
}
class Factory{
public static IFood getInstance(String food){
if("Apple".equals(food)){
return new Apple();
}else if("Milk".equals(food)){
return new Milk();
}else{
return null;
}
}
}
public class JavaDemo{
public static void main(String[] args){
IFood food = Factory.getInstance(args[0]);
food.eat();
}
}
如果在日后进行子类扩充的时候只需要修改Factory程序类即可实现。
5 代理设计模式(Proxy)
代理设计的主要功能是可以帮助用户将所有的开发注意力只集中在核心业务功能的处理上。
interface Eat{
public abstract void eat();
}
class EatReal implements Eat{
public void eat(){
System.out.println("[真实业务] 吃");
}
}
class EatProxy implements Eat{
private Eat eat;
public EatProxy(Eat eat){
this.eat = eat;
}
public void eat(){
this.prepare();
this.eat.eat();
this.clean();
}
public void prepare(){
System.out.println("[代理业务] 1、准备食材");
System.out.println("[代理业务] 2、处理食材");
}
public void clean(){
System.out.println("[代理业务] 3、收拾餐具");
}
}
public class JavaDemo{
public static void main(String[] args){
Eat eat = new EatProxy(new EatReal());
eat.eat();
}
}
代理设计模式的主要特点是:一个接口提供有两个子类,其中一个子类是真是业务操作类,另外一个主题是代理业务操作类,没有代理业务操作,真实业务无法进行。
6 抽象类与接口的区别
在实际的开发之中可以发现抽象类和接口的定义形式是非常相似的,这一点从JDK 1.8开始实际上就特别的明显了,因为在JDK 1.8里面接口也可以定义default或static方法了,但是这两者依然是有着明显的的定义区别与使用区别的。
No. | 区别 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义关键字 | abstract 抽象类名称 {} | interface 接口名称 {} |
2 | 组成 | 抽象方法、静态方法、普通方法、全局常量、成员属性 | 抽象方法、全局常量、default普通方法、静态方法 |
3 | 权限 | 可以使用各种权限定义 | 只能使用public定义 |
4 | 子类使用 | 子类通过extends关键字可以继承一个抽象类 | 子类通过implements关键字可以继承多个接口 |
5 | 两者关系 | 抽象类可以通过implements继承多个接口 | 接口不能继承类,但可以通过extends继承多个接口 |
6 | 使用(两者相似) | 1.抽象类或接口必须定义子类 2. 子类一定要覆写抽象类或接口的全部抽象方法 3. 通过子类向上转型实现抽象类或接口对象实例化 |
当抽象类和接口都可以使用的情况下优先考虑接口,因为接口可以避免子类的单继承局限。另外从一个正常的设计角度而言,也需要先从接口来进行项目的整体设计。