- 0
- 350 words
上一篇我们拆解了多态的底层机制——动态绑定、向上转型、向下转型、编译时类型 vs 运行时类型。掌握了这些基本功,这篇就该看看真正的工程级应用了。
老实说,如果你只是学语法,多态似乎没什么了不起的。但到了实际项目中,多态是让你的代码从「能跑」变成「好改」的关键之一。
一、参数多态化——写一个通用的方法
多态最常见的用法就是写一个参数为父类类型的方法,调用时可以传任意子类对象。这是「面向接口编程」的基础。
1.1 场景
假设你有一个动物园系统,要给所有动物喂食:
// 不用多态——硬伤
public void feedDog(Dog dog) { /* 喂狗 */ }
public void feedCat(Cat cat) { /* 喂猫 */ }
public void feedBird(Bird bird) { /* 喂鸟 */ }
// ...新增一种动物,就要多写一个方法
// 用多态——优雅
public void feed(Animal animal) {
animal.eat(); // 不管是什么动物,都执行 eat()
}
两种方案的差距不仅仅是代码量。多态的版本:
- 新增动物时,不需要改
feed()方法 - 调用方只需知道
Animal类型,不需要关心具体子类 - 方法的重用性达到了最大
1.2 经典案例:排序与比较器
Java 标准库中最常见的参数多态就是 Collections.sort():
List numbers = Arrays.asList(3, 1, 4, 1, 5);
Collections.sort(numbers); // 默认升序
// 传一个 Comparator 接口的实现,改变排序行为
Collections.sort(numbers, (a, b) -> b - a); // 降序
// 自定义对象的排序,也靠多态
Collections.sort(students, new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
return s1.getScore() - s2.getScore();
}
});
Collections.sort() 接受 List 和 Comparator 两个接口类型的参数。不管你传什么类型的数据、什么规则的比较器,sort 方法都能处理——这就是多态的价值。
二、返回类型多态——工厂方法模式
多态不只体现在参数上,返回值也一样。你可以声明返回一个父类类型,但实际返回的是子类对象。
// 简单的工厂模式
public class AnimalFactory {
public static Animal createAnimal(String type) {
switch (type) {
case "dog": return new Dog();
case "cat": return new Cat();
case "bird": return new Bird();
default: throw new IllegalArgumentException("未知动物类型");
}
}
}
// 调用方完全不需要知道具体类型
Animal a = AnimalFactory.createAnimal("dog");
a.makeSound(); // 多态调用的还是 Dog 的 makeSound()
这是工厂模式的核心——调用方只和父类(接口)打交道,创建逻辑被封装在工厂内部。新增动物类型只需要扩展子类,调用方代码不动。
三、多态数组——统一管理不同子类
多态让数组可以存放不同类型的对象,只要它们有共同的父类:
Animal[] zoo = new Animal[3];
zoo[0] = new Dog("旺财", 3);
zoo[1] = new Cat("咪咪", 2);
zoo[2] = new Bird("啾啾", 1);
// 统一遍历,不需要知道每个元素的具体类型
for (Animal animal : zoo) {
animal.makeSound(); // 每个都调用自己的版本
}
有没有觉得很眼熟?没错,ArrayList 内部就是维护了一个 Object[] 数组——所有类都是 Object 的子类,所以 ArrayList 能存任何类型。
四、实战案例:支付系统
来做一个更贴近真实开发的案例——支付系统。你要设计一个支持多种支付方式的系统:
// 1. 定义抽象的支付能力
abstract class Payment {
protected String orderId;
protected double amount;
public Payment(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
}
// 核心抽象方法——每种支付方式自己实现
public abstract boolean pay();
// 通用的支付日志
public void logPayment() {
System.out.println("[" + getClass().getSimpleName() + "] " +
"订单: " + orderId + ", 金额: " + amount);
}
}
// 2. 各支付方式
class WechatPay extends Payment {
public WechatPay(String orderId, double amount) {
super(orderId, amount);
}
@Override
public boolean pay() {
logPayment();
System.out.println("调用微信支付 SDK... 支付成功");
return true;
}
}
class Alipay extends Payment {
public Alipay(String orderId, double amount) {
super(orderId, amount);
}
@Override
public boolean pay() {
logPayment();
System.out.println("调用支付宝 SDK... 支付成功");
return true;
}
}
class CreditCardPay extends Payment {
public CreditCardPay(String orderId, double amount) {
super(orderId, amount);
}
@Override
public boolean pay() {
logPayment();
System.out.println("调用信用卡网关... 支付成功");
return true;
}
}
// 3. 支付处理服务
class PaymentService {
public void processPayment(Payment payment) {
// 统一处理——不管什么支付方式,都走这里
System.out.println("=== 开始处理订单 " + payment.orderId + " ===");
boolean success = payment.pay(); // 多态调用
if (success) {
System.out.println("支付完成,发送通知...");
} else {
System.out.println("支付失败,记录异常...");
}
}
}
// 4. 测试
public class PaymentDemo {
public static void main(String[] args) {
PaymentService service = new PaymentService();
// 工厂配合调用——调用方完全不知道具体实现
Payment[] payments = {
new WechatPay("WX001", 99.9),
new Alipay("ALI001", 199.0),
new CreditCardPay("CC001", 999.0)
};
for (Payment p : payments) {
service.processPayment(p); // 多态
}
}
}
这个例子中你看到的关键点:
PaymentService.processPayment()的参数是抽象类Payment,传什么子类进来都行- 新增一种支付方式(比如 Apple Pay),不需要改
PaymentService的任何代码——只需新增一个子类 logPayment()在抽象类里定义,所有子类复用这个通用逻辑getClass().getSimpleName()获取的是实际运行时类型的简单类名,不是声明类型——这也是动态绑定的体现
五、多态与设计原则
多态不只是一种语法,它背后站着一条重要的设计原则:
开闭原则(Open-Closed Principle):对扩展开放,对修改关闭。
什么意思呢?就是系统应该允许你在不修改已有代码的前提下,添加新功能。多态是实现这个原则的主要手段之一。
回头看支付系统的例子:
- 新增 Apple Pay → 写一个
ApplePay extends Payment→ 已有的PaymentService一行不用改 - 这就是「对扩展开放」:随时可以新增
- 这也是「对修改关闭」:已有的稳定代码不动
5.1 多态 vs if-else
很多人一开始写代码习惯用 if-else 来做类型判断:
// 多态之前——丑陋的 if-else
public void processPayment(String type, String orderId, double amount) {
if ("wechat".equals(type)) {
// 调用微信支付
} else if ("alipay".equals(type)) {
// 调用支付宝支付
} else if ("creditcard".equals(type)) {
// 调用信用卡支付
} else {
throw new RuntimeException("不支持的支付方式");
}
}
这段代码的问题:
- 每新增一种支付方式,就要改这个方法,加一个
else if - 这个方法的代码会越来越长,越来越难维护
- 所有支付方式的调用逻辑耦合在了同一个方法里
用多态重构之后:
- 每种支付方式有自己的类,各管各的
- 新增支付方式不需要改任何现有代码
- 调用方的代码极其精简——一行
payment.pay()搞定一切
当然,这不是说 if-else 本身是万恶之源。小规模和简单场景下,if-else 反而更直观。但当业务逻辑的分支越来越多的时候,多态是更优雅的解法。
六、总结
前后两篇,我们完整地走过了多态的底层原理和工程实践:
- 参数多态化:写一个处理父类的方法,子类都能传
- 返回类型多态:工厂模式,调用方只跟父类打交道
- 多态数组:统一管理和遍历不同子类
- 实战案例:支付系统——抽象类设计、工厂配合、开闭原则
- 多态的哲学:用多态替代 if-else,对扩展开放对修改关闭
学完了封装、继承、多态,你已经掌握了面向对象的三大核心特性。这个基础之后,下一个话题是 接口——它是抽象类思想的进一步延伸,而且在实际工程中,接口比抽象类的出场率还要高得多。
接口之于 Java,就像契约之于社会——明确了双方的责任边界,降低了彼此依赖的耦合度。