使用仓颉语言理解 SOLID 原则:概念、实战与踩坑总结

为什么要学 SOLID?

在开发项目时,我们每天都在"改需求":

  • 产品经理说"再加一种支付方式"
  • 后端说"换一套登录接口"
  • 设计师说"按钮样式统一换"

如果代码耦合严重,每一次改动都会牵一发动全身。

SOLID 五原则就是为了让"改动"变得安全、可预期、可测试,最终让"未来的自己"少掉两根头发。

SOLID 速览

缩写 全称 一句话记忆
SRP Single Responsibility Principle单一职责原则 一个类只负责一件事
OCP Open-Closed Principle开闭原则 对扩展开放,对修改关闭
LSP Liskov Substitution Principle里氏替换原则 子类必须能无缝替换父类
ISP Interface Segregation Principle接口隔离原则 别让客户端依赖它不需要的接口
DIP Dependency Inversion Principle依赖倒置原则 依赖抽象,而非具体实现

下面用 仓颉 代码逐一拆解

S -- Single Responsibility Principle 单一职责原则

概念:一个类只能因为"一个原因"而改变。

反例:UserManager 同时管注册、发邮件、存数据库。

cangjie 复制代码
class User {}

// ❌ 违反 SRP:UserManager 负责三件事
class UserManager {
    func registerUser(email: String, password: String): Bool {
        // 1. 注册逻辑
        return true
    }

    func sendWelcomeEmail(to: String, user: User): Unit {
        // 2. 发邮件逻辑
    }

    func saveUserData(user: User): Unit {
        // 3. 持久化逻辑
    }
}

重构:把三件事拆成三个类,每个类只有一个"变更理由"。

cangjie 复制代码
// ✅ 负责注册
class UserRegistrationService {
    func register(email: String, password: String): Bool {
        // 仅处理注册
        return true
    }
}

// ✅ 负责邮件
class EmailService {
    func sendWelcomeEmail(to: String, user: User): Unit {
        // 仅处理邮件
    }
}

// ✅ 负责持久化
class UserRepository: Unit {
    func save(user: User) {
        // 仅处理数据库/磁盘
    }
}

收益:

  • 邮件模板要改?只动 EmailService
  • 存储换成 Realm?只动 UserRepository

O -- Open-Closed Principle 开闭原则

概念:新增功能时,不修改老代码,只添加新代码。 反例:用 match 判断支付类型,每加一种支付方式都要改老文件。

cangjie 复制代码
// ❌ 违反 OCP:新增支付类型必须改 PaymentProcessor
class PaymentProcessor {
    func process(tp: String, amount: Float64): Unit {
        match (tp) {
            case "creditCard" => println("刷卡 ${amount}")
            case "paypal" => println("PayPal ${amount}")
            case _ => println("不支持")
        }
    }
}

重构:面向协议编程(POP),把"可变点"封装成协议。

cangjie 复制代码
// 1. 定义不变协议
interface PaymentMethod {
    func process(amount: Float64): Unit
}

// 2. 想加多少种支付就加多少类
class CreditCard <: PaymentMethod {
    public func process(amount: Float64): Unit {
        print("刷卡 ${amount}")
    }
}

class PayPal <: PaymentMethod {
    public func process(amount: Float64): Unit {
        print("PayPal ${amount}")
    }
}

// 3. 处理器对扩展永远关闭修改
class PaymentProcessor {
    func process(method: PaymentMethod, amount: Float64): Unit {
        method.process(amount) // 多态,无需 switch
    }
}

// 4. 新增 Apple Pay,老文件一行不动
class ApplePay <: PaymentMethod {
    public func process(amount: Float64): Unit {
        print("Apple Pay ${amount}")
    }
}

L -- Liskov Substitution Principle 里氏替换原则

概念:父类出现的地方,子类必须能无异常替换;子类不能"违约"。 反例:Bird 协议要求会飞,Penguin 被迫实现 fly() 却 crash。

cangjie 复制代码
// ❌ 违反 LSP:Penguin 不能飞,却强行 override
open class Bird {
    public open func fly(): Unit {
        println("飞")
    }
}

class Penguin <: Bird {
    public override func fly(): Unit {
        throw Exception("企鹅不会飞!") // 运行时爆炸
    }
}

func makeFly(bird: Bird) {
    bird.fly() // 传 Penguin 会崩溃
}

重构:把"飞"行为抽象成更小的协议,让真正的"飞行者"去遵守。

cangjie 复制代码
// 1. 仅飞行者才需要实现
interface Flyable {
    func fly(): Unit
}

// 2. 企鹅不会飞,就不实现 Flyable
class Eagle <: Flyable {
    public func fly(): Unit {
        println("鹰击长空")
    }
}

class Penguin {
    public func swim(): Unit {
        println("企鹅游泳")
    }
}

// 3. 高阶函数只接受 Flyable,类型安全
func makeFly(f: Flyable): Unit {
    f.fly()
}

I -- Interface Segregation Principle 接口隔离原则

概念:客户端不应被迫依赖它用不到的接口。 反例:Worker 协议同时要求 work() 和 eat(),机器人被迫空实现 eat()。

cangjie 复制代码
// ❌ 违反 ISP:Robot 不需要 eat,却必须"假实现"
interface Worker {
    func work(): Unit
    func eat(): Unit
}

class HumanWorker <: Worker {
    public func work(): Unit {
        print("人工 work")
    }
    public func eat(): Unit {
        print("人工 eat")
    }
}

class RobotWorker <: Worker {
    public func work(): Unit {
        print("机器人 work")
    }
    public func eat(): Unit { /* 机器人不吃,空实现 */ }
}

重构:拆成"小接口",按需组合。

cangjie 复制代码
// 1. 行为细分
interface Workable {
    func work(): Unit
}

interface Eatable {
    func eat(): Unit
}

// 2. 人类两个都要
class HumanWorker <: Workable & Eatable {
    public func work(): Unit {
        print("人工 work")
    }
    public func eat(): Unit {
        print("人工 eat")
    }
}

// 3. 机器人只实现 Workable
class RobotWorker <: Workable {
    public func work(): Unit {
        print("机器人 work")
    }
}

D -- Dependency Inversion Principle 依赖倒置原则

概念:

  1. 高层不依赖低层,二者都依赖抽象。
  2. 抽象不依赖细节,细节依赖抽象。

反例:DataManager 直接初始化 LowLevelStorage,换数据库要改高层。

cangjie 复制代码
// ❌ 违反 DIP:高层依赖具体实现
class LowLevelStorage {
    func store(data: String): Unit {
        println("存磁盘 ${data}")
    }
}

class DataManager {
    private let storage = LowLevelStorage() // 硬编码具体类
    func save(data: String): Unit {
        storage.store(data)
    }
}

重构:通过协议 + 依赖注入(DI)倒转依赖方向。

cangjie 复制代码
// 1. 抽象协议
interface Storage {
    func store(data: String): Unit
}

// 2. 具体实现
class DiskStorage <: Storage {
    public func store(data: String): Unit {
        println("存磁盘 ${data}")
    }
}

class CloudStorage <: Storage {
    public func store(data: String): Unit {
        println("存云端 ${data}")
    }
}

// 3. 高层只依赖抽象
class DataManager {
    DataManager(private let storage!: Storage) {}

    func save(data: String): Unit {
        storage.store(data)
    }
}

func demo(): Unit {
    // 4. 使用时自由切换
    let manager = DataManager(storage: CloudStorage())
    manager.save("Hello DIP")
}

实战总结与踩坑

  1. SRP 最难的是"粒度"

    拆太细 → 类爆炸;拆太粗 → UserManager类。

    判断标准:如果一段逻辑因为两个完全不同的需求而变更,就拆分。

  2. OCP 在 cangjie 最自然的工具是协议 + 泛型 + 扩展。

    可以在不修改原有方法实现的情况下新增实现

  3. LSP 常被忽略,尤其在"继承狂热"场景。

    建议:优先用组合+协议,少用继承;如果必须继承,子类只能加强、不能削弱父类行为(返回值更具体、异常更少)。

  4. ISP可以与仓颉的协议继承一起使用

    例如 public interface Equatable<T> <: Equal<T> & NotEqual<T> 就是 ISP 思想。

  5. DIP 是架构级别的"依赖倒转器"。

结语

SOLID 不是教条,而是"让代码拥抱变化"的底层思维。

当你习惯把"可能变化"的方向抽象成协议、把"单一理由"拆成独立模块、把"继承"换成"组合",你会发现:

  • 单元测试更好写(Mock 协议即可)
  • Code Review 更少冲突(改动隔离)
  • 新人上手更快(类名即职责)

下一次需求变更来临,你可以优雅地新增一个文件,而不是在旧代码里"打补丁"。

相关推荐
爱笑的眼睛116 小时前
HarmonyOS 应用开发深度解析:ArkTS 声明式 UI 与状态管理最佳实践
华为·harmonyos
安卓开发者6 小时前
鸿蒙Next ArkWeb进程解析:多进程架构如何提升Web体验
前端·架构·harmonyos
damo王7 小时前
鸿蒙(HarmonyOS) 历史
华为·harmonyos
爱笑的眼睛118 小时前
HarmonyOS声明式UI开发:深入ArkUI与状态管理实践
华为·harmonyos
爱笑的眼睛118 小时前
HarmonyOS 应用开发进阶:深入 Stage 模型与 ArkUI 声明式开发实践
华为·harmonyos
2501_919749038 小时前
鸿蒙:更改状态栏、导航栏颜色
华为·harmonyos
2501_919749039 小时前
鸿蒙:@Builder 和 @BuilderParam正确使用方法
华为·harmonyos
爱笑的眼睛119 小时前
HarmonyOS应用开发:深入解析Stage模型与UIAbility
华为·harmonyos
HMSCore12 小时前
Cloud Foundation Kit启动预加载,赋能喜马拉雅秒启秒开流畅体验
harmonyos