第15章:SOLID原则总结与对比——五大原则的内在联系,如何在实际项目中综合运用

说实话,讲到这里,SOLID的五个原则我们都已经逐个聊过了。但有个问题我一直想强调——这五个原则不是孤立的。你想想看,它们就像五个手指,各有各的用处,但握成拳头才有力气。

我在项目中见过不少团队,把SOLID当成教条来背。有人死磕单一职责,结果类拆得比芝麻还碎;有人迷恋开闭原则,接口设计得跟俄罗斯套娃似的。嗯,这其实走偏了。

今天我们就来做个总结。把五个原则串起来,看看它们到底怎么配合,怎么在实际项目中落地。

一、SOLID原则的内在联系

先画张图,让你一眼看明白它们的关系。

SOLID 五大原则内在联系图 SOLID 核心 SRP 单一职责 OCP 开闭原则 LSP 里氏替换 ISP 接口隔离 DIP 依赖倒置 职责清晰 → 易扩展 抽象稳定 → 易扩展 继承规范 → 可替换 接口精简 → 低耦合 依赖抽象 → 整体解耦 SRP + OCP 定义结构,LSP 保证继承安全,ISP 控制接口粒度,DIP 实现整体解耦

从这张图你能看到什么?我来说说我的理解。

SRP 和 OCP 是基础。一个类只做一件事,你才有底气去扩展它。我在重构一个支付模块时深有体会——原来一个 PaymentService 既管验签又管记账又管通知,想加个新支付方式,改代码改到崩溃。拆成单一职责后,加新渠道只需要写个新类,完全符合 OCP。

LSP 是 OCP 的保障。你开放扩展了,但子类不能乱来。我见过有人继承了一个基类,重写方法时直接抛异常——这其实就违反了 LSP。调用方根本不敢用多态,OCP 也就成了空话。

ISP 和 DIP 是解耦的利器。接口别太胖,依赖别太具体。这两个原则配合起来,你的系统才能做到「高内聚、低耦合」。

二、五个原则的对比

为了方便你记忆,我整理了一张表。你写代码时拿不准,可以回来翻翻。

原则 核心思想 关注点 违反时的症状 我常用的落地手法
SRP 一个类只有一个变更理由 类的职责边界 一个类改来改去,每次原因不同 先写职责描述,超过一句话就拆
OCP 对扩展开放,对修改关闭 系统的可扩展性 加功能就要改老代码 抽象接口 + 策略模式
LSP 子类必须能替换父类 继承的正确性 多态调用时行为异常 子类不削弱父类的前置条件
ISP 接口要小而专 接口的粒度 实现类被迫实现无用方法 按调用方拆分接口
DIP 依赖抽象,不依赖具体 模块间的耦合 高层模块直接依赖底层细节 依赖注入 + 工厂模式
核心观点: SOLID 不是五个独立的原则,而是一个有机整体。SRP 和 OCP 决定了系统的骨架,LSP 保证了骨架的强度,ISP 和 DIP 让骨架之间的连接更灵活。

三、实际项目中如何综合运用

光讲理论没意思。我拿一个真实项目来举例——订单处理系统。这个场景几乎每个做后端的人都会遇到。

3.1 场景描述

假设我们要设计一个订单处理模块。订单有多种类型:普通订单、会员订单、促销订单。每种订单的计价、优惠、积分计算都不一样。

你想想看,如果不用 SOLID,你会怎么写?大概率是一个 OrderService 类,里面塞满 if-else。嗯,我早期就是这么干的,结果维护了三个月就想跑路。

3.2 用 SOLID 重构

我们一步步来。

第一步:SRP 拆分职责

先把大胖子拆开。订单处理至少包含:

  • 订单数据管理(OrderRepository)
  • 价格计算(PriceCalculator)
  • 优惠策略(DiscountStrategy)
  • 积分处理(PointsHandler)
  • 通知发送(NotificationService)

每个类只干一件事。这样改起来,你不用担心牵一发动全身。

第二步:OCP + 策略模式

不同类型的订单,计价和优惠逻辑不同。我们用策略模式来处理。

// 抽象策略接口
class IDiscountStrategy {
public:
    virtual double calculate(double price) = 0;
    virtual ~IDiscountStrategy() = default;
};

// 具体策略
class NormalDiscount : public IDiscountStrategy {
public:
    double calculate(double price) override {
        return price; // 无优惠
    }
};

class MemberDiscount : public IDiscountStrategy {
public:
    double calculate(double price) override {
        return price * 0.85; // 会员85折
    }
};

class PromoDiscount : public IDiscountStrategy {
public:
    double calculate(double price) override {
        return price > 100 ? price - 20 : price; // 满100减20
    }
};

加新类型?写个新策略类就行。老代码不用动。这就是 OCP 的威力。

第三步:LSP 保证安全

写子类时要注意。比如你写一个 VIPMemberDiscount 继承 MemberDiscount,重写 calculate 时不能把折扣改成 1.2 倍——这违反了 LSP。调用方预期是打折,结果你给涨价了,业务逻辑全乱套。

避坑指南: 我曾经在项目中遇到一个坑——子类重写方法时,把参数范围扩大了。父类接受正数,子类接受负数。结果调用方传了负数,父类逻辑直接崩溃。这就是典型的 LSP 违反。记住:子类的前置条件不能比父类更严格,后置条件不能比父类更弱。

第四步:ISP 精简接口

别搞一个大而全的 IOrderProcessor 接口。不同的调用方只需要不同的能力:

// 按职责拆分接口
class IPriceCalculable {
public:
    virtual double calculatePrice() = 0;
};

class IDiscountable {
public:
    virtual double applyDiscount(double price) = 0;
};

class IPointsEarnable {
public:
    virtual int calculatePoints(double amount) = 0;
};

这样,一个订单类只需要实现它需要的接口。不会出现「我明明不需要积分,却被迫实现一个空的 calculatePoints」的尴尬。

第五步:DIP 解耦依赖

高层模块(比如 OrderService)不要直接 new 具体的策略类。用依赖注入:

class OrderService {
public:
    // 依赖抽象,不依赖具体
    OrderService(std::shared_ptr<IDiscountStrategy> strategy,
                 std::shared_ptr<IPointsEarnable> pointsHandler)
        : strategy_(strategy), pointsHandler_(pointsHandler) {}

    void processOrder(Order& order) {
        double finalPrice = strategy_->calculate(order.getPrice());
        int points = pointsHandler_->calculatePoints(finalPrice);
        // ... 处理订单
    }

private:
    std::shared_ptr<IDiscountStrategy> strategy_;
    std::shared_ptr<IPointsEarnable> pointsHandler_;
};

这样,OrderService 根本不关心你用的是哪种折扣策略。换策略?换个注入对象就行。

四、综合运用的节奏感

你可能要问了:每个项目都这么搞,是不是太复杂了?

我的建议是:别过度设计。SOLID 是工具,不是枷锁。

  • 小项目、原型阶段:先跑起来再说。等代码开始「发臭」了再重构。
  • 核心模块、频繁变动的部分:优先应用 SOLID。这部分值得投入。
  • 团队协作的项目:接口和抽象要提前定好。不然每个人写出来的风格都不一样,后期合并代码会想哭。
我的个人习惯: 写代码前,先花10分钟画一下类图。把 SRP 的职责边界、OCP 的扩展点、DIP 的依赖方向标出来。这10分钟,往往能省下后面10小时的改代码时间。

五、总结

SOLID 五个原则,说白了就是一件事:让代码更容易被修改

SRP 让你知道改哪里,OCP 让你不改老代码,LSP 让你放心改,ISP 让你少改,DIP 让你改的时候不影响别人。

我在项目中见过太多「不敢改代码」的团队。一改就崩,一崩就加班,一加班就骂娘。其实根源就是设计没做好。SOLID 不能解决所有问题,但它能帮你把系统的「脆弱性」降到最低。

嗯,这一章就到这里。记住:原则是死的,人是活的。理解背后的思想,比背下五个名字重要得多。


公众号:蓝海资料掘金营,微信 deep3321