SOLID 设计原则概述
SOLID 是 面向对象程序设计中,非常重要的原则,遵循此原则进行编程,意在提高代码的可维护性、可扩展性、可测试性,并减少耦合。
缩写 | 名称 | 含义 |
---|---|---|
S | Single Responsibility Principle | 单一职责 |
O | Open/Closed Principle | 开闭原则 |
L | Liskov Substitution Principle | 里氏替换 |
I | Interface Segregation Principle | 接口隔离 |
D | Dependency Inversion Principle | 依赖倒置 |
单一职责
一个类只负责一项职责。也就是说,一个类应该只有一个引起它变化的原因。
- 反面教材🚫
swift
class UserManager {
func registerUser(username: String, password: String) {
// 注册逻辑
print("注册一个用户: \(username)")
// 发送邮件(这属于另一种职责)
sendWelcomeEmail(to: username)
}
private func sendWelcomeEmail(to username: String) {
print("发邮件给: \(username)")
}
}
这里 ,UserManager 同时负责"注册用户"和"发送邮件",职责不单一。另外,发送邮件的业务 原则上只能通过调用sendWelcomeEmail来实现。而这里,调用registerUser也能触发 邮件的发送。不合理。
- 正确示例✅
swift
class UserRegistrationService {
func register(username: String, password: String) {
print("User registered: \(username)")
}
}
class EmailService {
func sendWelcomeEmail(to username: String) {
print("Welcome email sent to \(username)")
}
}
// 使用时
let registrationService = UserRegistrationService()
let emailService = EmailService()
registrationService.register(username: "Tom", password: "123")
emailService.sendWelcomeEmail(to: "Tom")
开闭原则
类、模块、函数应该对扩展开放,对修改关闭。即:不要修改已有代码来适配新需求,而是通过扩展实现。
- 反面教材🚫
swift
class PaymentProcessor {
func pay(amount: Double, method: String) {
if method == "wechat" {
print("微信支付: \(amount)")
} else if method == "alipay" {
print("支付宝支付: \(amount)")
} else {
print("其他支付")
}
}
}
这样写,每新增一种支付方式,都要修改 pay方法。
- 正确示例✅
swift
protocol PaymentMethod {
func pay(amount: Double)
}
class WeChatPay: PaymentMethod {
func pay(amount: Double) {
print("微信支付: \(amount)")
}
}
class Alipay: PaymentMethod {
func pay(amount: Double) {
print("支付宝支付: \(amount)")
}
}
class PaymentProcessor {
func process(amount: Double, using method: PaymentMethod) {
method.pay(amount: amount)
}
}
// 使用
let processor = PaymentProcessor()
processor.process(amount: 100, using: WeChatPay())
processor.process(amount: 100, using: Alipay())
这样,扩展支付方式时,只需新增类,不改旧代码。
在面向对象编程中,一个类要做什么,考虑扩展需要,最好设计为接口(protocol),而不是直接设计为函数及实现。这也是面向协议编程的思想。
里氏替换
子类必须能够替代父类使用,且不影响程序的正确性。
- 反面教材🚫
swift
class Bird {
func fly() {
print("鸟 在飞")
}
}
class Penguin: Bird {
override func fly() {
fatalError("企鹅不能飞")
}
}
这里, 子类通过重载写自己的业务,但是修改了父类方法实现。这样写代码不好。
- 正确示例✅
swift
protocol Bird {
func eat()
}
protocol FlyingBird: Bird {
func fly()
}
class Sparrow: FlyingBird {
func eat() { print("麻雀 吃") }
func fly() { print("麻雀 飞") }
}
class Penguin: Bird {
func eat() { print("企鹅 吃") }
}
通过继承接口,按需得到相应的能力/做需要的事情
协议的继承
接口隔离
不应强迫客户端依赖它不使用的方法。也就是:设计接口 应精简、专一。外界按需继承。
- 反面教材🚫
swift
protocol Worker {
func work()
func eat()
}
class Robot: Worker {
func work() { print("机器人工作") }
func eat() { } // 不需要
}
- 正确示例✅
swift
protocol Workable {
func work()
}
protocol Eatable {
func eat()
}
class Human: Workable, Eatable {
func work() { print("人 工作") }
func eat() { print("人 吃饭") }
}
class Robot: Workable {
func work() { print("机器人工作") }
}
依赖倒置
高层模块不应依赖低层模块,两者都应依赖抽象。换句话说:依赖接口,不依赖实现。(解耦)
- 反面教材🚫
swift
class MySQLDatabase {
func save(data: String) {
print("保存到 MySQL: \(data)")
}
}
class UserManager {
let db = MySQLDatabase()
func saveUser(name: String) {
db.save(data: name)
}
}
这样写,类 UserManager 直接依赖具体数据库实现,耦合严重。
- 正确示例✅
swift
protocol Database {
func save(data: String)
}
class MySQLDatabase: Database {
func save(data: String) {
print("保存到 MySQL: \(data)")
}
}
class SQLiteDatabase: Database {
func save(data: String) {
print("保存到 SQLite: \(data)")
}
}
class UserRepository {
private let database: Database
init(database: Database) {
self.database = database
}
func saveUser(name: String) {
database.save(data: name)
}
}
// 使用时 可自由替换数据库
let repo = UserRepository(database: SQLiteDatabase())
repo.saveUser(name: "Tom")
依赖倒置让代码更易于扩展、测试与切换实现。
通过公共协议 来实现类与类的交互,实现解耦