针对接口编程”的真正含义是“针对超类型编程”,它利用了多态的特性。

更明确的来说就是,一个变量 a声明类型应该是超类型A,所谓的超类型一般是抽象类接口。超类型强调的是,它与它的所有派生类共有的“特性”。

这样做的好处是,变量 a 可以指向超类型 A 的任意一个派生类,并且 a 调用超类型A 中的任意一个方法都不会出错。这时 a 进行任何操作,其实都是动态决定的,而不是硬编码,这样的代码也更加有弹性。

比如,我们有下面的继承关系:

interface Animal {
    void makeSound();
}

class Dog implements Animal {
    public void makeSound() {
        bark();
    }
    
    public void bark() {
        // 汪汪叫
    }
}

class Cat implements Animal {
    public void makeSound() {
        meow();
    }

    public void meow() {
        // 喵喵叫
    }
}

Animal 是一个接口(也可以是抽象类),其中包含了 makeSound 方法。

DogCat 都是 Animal 的派生类,它们除了实现 makeSound 方法外,还有各自的 barkmeow 方法。

makeSound 方法代表了 Animal 及其所有派生类的“共性”,而 barkmeow 则是“非共性”。

针对实现编程

如果我们编写了下面代码:

Dog d = new Dog();
d.bark();

那么这种代码就是“针对实现编程”,因为 d 的类型为 Dog,是一个具体类型,而不是一个抽象类型。并且 bark 方法,也是 Dog 所特有的一种操作,而不是共性。

针对接口编程

再来看下面代码:

Animal a = new Dog();
a.makeSound();

现在的代码就是“针对接口编程”了,因为 a 的类型是 Animal,是一个抽象类型,而不是一个具体类型。此时 a 调用 makeSound 方法,代表的是所有的 Animal 都能进行的一种操作。

那么再进一步,我们可以将对象的创建封装成一个方法,如下:

public static Animal getAnimal(String name) {
    Animal a = null;
    
    if (name.equals("dog")) {
        a = new Dog();
    } else if (name.equals("cat")) {
        a = new Cat();
    }
    
    return a;
} 

这样使用 getAnimal 方法:

String name = ... ;         // name 可以动态决定
Animal a = getAnimal(name);
a.makeSound();

可以看到,name 变量可以动态决定,从而 a 也是动态决定的,调用 makeSound 时不需要关心真正的对象是什么,而且代码也不会出错。