发布于2026-07-21 阅读(0)
扫一扫,手机访问
刚读完那本经典的《设计模式》,脑子里蹦出个老生常谈的话题——面向接口编程。很多人觉得这概念抽象,其实它每天都在我们写的代码里打转。今天就用一个轮胎的例子,把这事儿掰扯清楚。
书里开篇就提到了一个设计原则,前辈们反复强调,但真正理解透的人不多:
针对接口编程,而不是针对实现编程
说实话,用面向对象语言写代码的时候,大家或多或少都在用这个原则,只是没意识到它的名字。一旦专门拎出来说“诶,什么是面向接口编程”,反而容易懵。反复琢磨之后才发现,这不就是日常编程里天天碰到的老问题嘛。
先看一个反面教材。假设有两种轮胎品牌:普利司通和米其林。轮胎的共同特性是都会转(roll)。于是我们写出了两个类:
class Bridgestone {
public void roll() {
System.out.print("Bridgestone is rolling.");
}
}
class Michelin {
public void roll() {
System.out.print("Michelin is rolling");
}
}
对于一辆装了普利司通轮胎的汽车,汽车的转动就是轮胎的转动:
class Car {
public void roll(Bridgestone tire) {
tire.roll();
}
}
问题来了——如果我给这辆车换上米其林轮胎呢?
Car car = new Car();
Michelin tire = new Mechilin();
car.roll(tire); // 编译错误
程序直接罢工。这就是典型的面向实现编程:变量死死绑定着某个具体类,换个品牌就崩。这种强烈的依赖关系,大大限制了代码的灵活性和可复用性。换句话说,你写死了,就动不了。
那怎么破?很简单——把轮胎的共同特性抽象出来。当需要转动轮胎时,我们只关心“它是个轮胎”,而不关心“它是什么牌的轮胎”。
interface Tire {
public void roll();
}
class Bridgestone implements Tire {
public void roll() {
System.out.print("Bridgestone is rolling.");
}
}
class Michelin implements Tire {
public void roll() {
System.out.print("Michelin is rolling");
}
}
接口 Tire 定义了“转动”这个契约,但具体实现延迟到各个子类里。现在汽车只需要面向接口:
class Car {
public void roll(Tire tire) {
tire.roll();
}
}
Car car = new Car();
BridgeStone tire1 = new Bridgestone();
Michelin tire2 = new Mechilin();
car.roll(tire1);
car.roll(tire2); // 两种轮胎都能转
汽车在转动时,根本不关心轮胎的品牌,只认准“是轮胎就行”。想换哪种就换哪种,代码纹丝不动。
从这个小例子,我们能总结出面向接口编程的两个核心特性:
客户无须知道他们使用对象的特定类型,只须对象有客户所期望的接口。
客户用汽车去转动轮胎时,不需要知道轮胎的具体品牌(是普利司通还是米其林),只要轮胎实现了 roll() 这个接口就够了。
客户无须知道他们使用的对象是用什么类来实现的,他们只须知道定义接口的抽象类。
客户调用 car.roll(tire) 时,根本不需要关心这个轮胎实例是用哪个子类 new 出来的,他只需要知道接口定义的方法签名——也就是 Tire 里写的 void roll()。
面向接口编程是面向对象设计的基石之一。用这种思路写代码,可复用性会明显提升——代码不再依赖具体实现,而是依赖抽象契约。后续维护、扩展、替换都变得轻松。说穿了,就是给自己留好余地,别把路堵死。