知用网
霓虹主题四 · 更硬核的阅读氛围

类继承与依赖注入对比:谁更适合现代软件设计

发布时间:2026-01-01 15:51:20 阅读:70 次

从一个电商订单说起

假设你在写一个电商系统的订单处理模块。一开始,所有订单都走微信支付。你很自然地写了个 Order ,然后让 WeChatPayOrder 继承它,加个支付方法。代码跑得挺好。

可没过多久,产品说要支持支付宝、银行卡,甚至未来可能接入数字货币。你开始头疼了。如果继续用继承,是不是得写一堆子类:AlipayOrder、BankOrder、DigitalCurrencyOrder?每个都重复一套流程,只改一点点支付逻辑。

类继承的甜蜜陷阱

继承听起来很直观:父子关系,代码复用。但问题就出在这“复用”上。你复用的不只是功能,还绑定了结构和行为。一旦父类变了,所有子类都得跟着动。就像你家装修,客厅改布局,结果厨房也莫名其妙被重装了一遍。

再看代码:

class Order {
    public void process() {
        validate();
        pay();
        sendNotification();
    }

    protected abstract void pay();
}

class WeChatPayOrder extends Order {
    protected void pay() {
        // 微信支付逻辑
    }
}

看着清爽,但每加一种支付方式,就得加一个类。更糟的是,测试也得跟着翻倍。你想换种支付方式?不好意思,得重新实例化一个类,甚至改构造逻辑。

依赖注入:把选择权交出去

换个思路。订单本身不关心你怎么付钱,它只关心“能付成”。那不如把支付方式当成一个零件,组装进去。

这时候依赖注入就派上用场了。你定义一个 PaymentService 接口,微信、支付宝各实现一份。订单类初始化时,外面把具体的支付服务塞进来。

interface PaymentService {
    void pay(double amount);
}

class Order {
    private PaymentService paymentService;

    public Order(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void process() {
        validate();
        paymentService.pay(totalAmount);
        sendNotification();
    }
}

你看,订单类现在干净了。加新支付方式?写个新实现,传进去就行。测试也方便,mock 一个假支付服务,立马跑单元测试。

场景决定选择

但这不是说继承就没用了。如果你在做一个图形编辑器,形状有圆形、矩形、三角形,它们都有面积、周长这些共性,用继承就很自然。因为这是典型的“是什么”的关系——矩形是一个形状。

而支付方式和订单之间是“用什么”的关系——订单用某种支付方式,而不是“订单是一种支付方式”。这种组合关系,更适合依赖注入。

再打个比方:继承像定制手机,所有功能焊死在主板上;依赖注入像模块化手机,摄像头、电池、存储都能换。现在大家为啥偏爱后者?因为变化太快,没人想为换个闪光灯就扔掉整台手机。

灵活性背后的代价

依赖注入也不是银弹。配置复杂了,尤其是项目一大,一堆 service、repository 层层注入,新手一看懵。Spring 框架帮你管理这些,但理解成本也上去了。

而继承简单直接,一眼看懂谁是谁的子类。小项目、稳定需求下,反而更省事。就像修自行车,补胎用胶水一粘就行,没必要上3D打印定制件。

设计的本质是取舍

真正重要的不是用哪个技术,而是搞清楚对象之间的关系。是“属于”还是“使用”?是稳定共性还是易变部分?

继承适合提取稳定不变的共性,比如用户基类里的 ID、创建时间。依赖注入适合封装可能变化的行为,比如日志输出到文件还是远程服务器。

高手写代码,不是背套路,而是像搭积木,知道哪块该固定,哪块该留插槽。类继承给你结构,依赖注入给你弹性。什么时候用哪个,取决于你想让系统哪里硬,哪里软。