从理论到实战,逐条拆解 + 代码示例 + 重构案例,让你一次掌握五大设计原则。
什么是 SOLID?
SOLID 是面向对象设计的五大原则,帮助开发者写出高内聚、低耦合、易测试、可扩展的代码。
- Single Responsibility Principle(单一职责)
- Open/Closed Principle(开闭原则)
- Liskov Substitution Principle(里氏替换)
- Interface Segregation Principle(接口隔离)
- Dependency Inversion Principle(依赖反转)
SRP:单一职责原则
一个类/模块只有一个理由去改变
把"因为需求 A 修改"和"因为需求 B 修改"拆成两个类。
❌ 反面示例
swift
class DataManager {
func fetchData() { /* 网络请求 */ }
func parseData(_ data: Data) { /* JSON 解析 */ }
func saveData(_ parsed: ParsedData) { /* 写入磁盘 */ }
func showAlert() { /* UI 弹窗 */ } // 又管 UI?
}
✅ 重构后:职责分离
swift
// 网络
class NetworkService {
func fetchData() async throws -> Data { /* ... */ }
}
// 解析
class JSONParser {
func parse<T: Decodable>(_ data: Data) throws -> T { /* ... */ }
}
// 存储
class CacheService {
func save<T: Encodable>(_ object: T, key: String) throws { /* ... */ }
}
- 好处:修改网络层时不会波及缓存逻辑,单元测试也更容易 mock。
OCP:开闭原则
对扩展开放,对修改关闭
加功能时尽量不改动旧代码,用协议 + 多态解决。
❌ 反面:每加一个形状就改 switch
swift
enum ShapeType { case circle, square }
func render(_ type: ShapeType) {
switch type {
case .circle: /* 画圆 */
case .square: /* 画方 */
}
}
✅ 重构:面向协议扩展
swift
protocol Renderer {
func render()
}
struct CircleRenderer: Renderer {
func render() { /* 画圆 */ }
}
struct SquareRenderer: Renderer {
func render() { /* 画方 */ }
}
// 新增三角形:零改动旧代码
struct TriangleRenderer: Renderer {
func render() { /* 画三角形 */ }
}
- 好处:新增形状只需再写一个
Renderer
,老代码稳如老狗。
LSP:里氏替换原则
子类必须能透明替换父类,不能破坏父类约定。
❌ 反面:正方形继承矩形导致异常
swift
class Rectangle {
var width: Double = 0
var height: Double = 0
func area() -> Double { width * height }
}
class Square: Rectangle {
override var width: Double {
didSet { height = width } // 破坏父类行为
}
}
func printArea(_ r: Rectangle) {
r.width = 5
r.height = 4
print(r.area()) // 期望 20,Square 却得到 16
}
✅ 重构:协议抽象
swift
protocol Shape {
func area() -> Double
}
struct Rectangle: Shape {
let width, height: Double
func area() -> Double { width * height }
}
struct Square: Shape {
let side: Double
func area() -> Double { side * side }
}
- 好处:
Square
无需伪装成Rectangle
,行为自然正确。
ISP:接口隔离原则
客户端不应被迫依赖它不需要的接口
把胖协议拆成小协议,按需组合。
❌ 反面:胖协议
swift
protocol Database {
func save()
func fetch()
func delete()
func backup()
}
class TinyCache: Database {
func save() { /* 只用到 save */ }
func fetch() { /* 空实现 */ }
func delete() { /* 空实现 */ }
func backup() { /* 空实现 */ } // 被迫实现
}
✅ 重构:小协议 + 组合
swift
protocol Savable {
func save()
}
protocol Fetchable {
func fetch() -> Data
}
class TinyCache: Savable {
func save() { /* 只需实现 save */ }
}
- 好处:实现类只关心自己需要的接口,避免"大而全"。
DIP:依赖反转原则
高层模块不依赖低层细节,两者都依赖抽象
通过构造函数/属性注入解耦。
❌ 反面:硬编码依赖
swift
class DataManager {
private let api = RemoteAPI() // 写死
func load() {
let data = api.fetch()
}
}
- 测试时想换成
MockAPI
很难。
✅ 重构:依赖注入
swift
protocol DataProvider {
func fetch() async throws -> Data
}
struct RemoteAPI: DataProvider { /* ... */ }
struct MockAPI: DataProvider { /* ... */ }
class DataManager {
private let provider: DataProvider
init(provider: DataProvider) {
self.provider = provider
}
func load() async throws {
let data = try await provider.fetch()
}
}
// 使用
let manager = DataManager(provider: RemoteAPI())
// 测试
let testManager = DataManager(provider: MockAPI())
- 好处:随时替换实现,单元测试秒变 mock。
综合案例:重构一个"用户服务"
需求
- 登录后获取用户信息
- 缓存到本地
- 支持远程 & 本地两种数据源
初始代码(违反 SRP+DIP)
swift
class UserService {
func login() {
// 网络请求
// JSON 解析
// 写磁盘
// 通知 UI
}
}
✅ SOLID 重构后
swift
// 1. 抽象数据源
protocol UserDataSource {
func fetchUser(id: String) async throws -> User
}
// 2. 实现
struct RemoteUserSource: UserDataSource { /* 网络 */ }
struct LocalUserSource: UserDataSource { /* 缓存 */ }
// 3. 解析器
struct UserParser {
func parse(_ data: Data) throws -> User { /* ... */ }
}
// 4. 仓库(高层模块)
class UserRepository {
private let remote: UserDataSource
private let local: UserDataSource
private let parser: UserParser
init(remote: UserDataSource, local: UserDataSource, parser: UserParser) {
self.remote = remote
self.local = local
self.parser = parser
}
func user(id: String) async throws -> User {
if let cached = try await local.fetchUser(id: id) { return cached }
let data = try await remote.fetchUser(id: id)
return try parser.parse(data)
}
}
- 单一职责:每个类只做一件事
- 开闭原则:新增
GraphQLUserSource
不改动旧代码 - 依赖反转:
UserRepository
只依赖UserDataSource
协议
总结速记表
原则 | 一句话 | Swift 技巧 |
---|---|---|
SRP | 一个类做一件事 | 拆小类、用协议 |
OCP | 加功能不改动旧代码 | 协议 + 多态 |
LSP | 子类必须可替换父类 | 用协议代替继承 |
ISP | 接口要小而专 | 小协议组合 |
DIP | 依赖抽象不依赖细节 | 构造函数注入 |