×

上一篇我们拆解了多态的底层机制——动态绑定、向上转型、向下转型、编译时类型 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() 接受 ListComparator 两个接口类型的参数。不管你传什么类型的数据、什么规则的比较器,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,就像契约之于社会——明确了双方的责任边界,降低了彼此依赖的耦合度。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章

20. 接口基础:语法、特性与抽象类对比

抽象类讲完了。你知道父类可以只声明方法签名,...

读出全部

18. 多态的本质:动态绑定、向上转型与向下转型

抽象类学完了,你现在应该知道 「父类只声明、...

读出全部