SOLID 介绍
面向对象编程(OOP)是一种程序设计范式,它使用"对象"来设计软件。对象可以包含数据(称为属性或字段)和操作数据的代码(称为方法)。SOLID 原则是面向对象设计和编程中的一组五个基本原则,它们有助于软件开发者设计易于管理和扩展的系统。这些原则在 Swift 编程语言中同样适用,如下所述:
- 单一职责原则(Single Responsibility Principle, SRP) :一个类应该只有一个引起它变化的原因。这意味着一个类应该只负责一件事情。在 Swift 中,你可以通过将功能拆分为多个专注于单一任务的类或结构体来实现这一点。
- 开放封闭原则(Open/Closed Principle, OCP) :软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着应该能够在不修改现有代码的情况下扩展一个类的功能。在 Swift 中,可以通过使用协议和扩展来实现,允许现有类型获得新功能而无需改变其源代码。
- 里氏替换原则(Liskov Substitution Principle, LSP) :子类型必须能够替换掉它们的基类型。这意味着如果类 A 是类 B 的子类,则应该可以在不改变程序期望结果的情况下,用类 B 替换类 A 的实例。在 Swift 中,遵循这一原则意味着子类应该遵守父类的契约,同时不引入破坏性行为。
- 接口隔离原则(Interface Segregation Principle, ISP) :不应该强迫客户端依赖于它们不使用的接口。在 Swift 中,这意味着应该避免创建大而全的协议,而是应该将它们分解为更小的、更专用的协议,使得实现类只需关注它们真正需要的部分。
- 依赖倒置原则(Dependency Inversion Principle, DIP) :高层模块不应该依赖于低层模块,它们都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。在 Swift 中,这通常意味着依赖于协议而不是具体的类,使得可以灵活地更换具体实现,而不会影响到高层模块的设计。
遵循这些 SOLID 原则可以帮助 Swift 开发者设计出更灵活、更容易维护和扩展的应用程序。
单一职责
A class should have just a unique reason to be changed, or in other words, a class should have a single responsibility.
单一职责即: 一个类改变的原因只有一个,也就是类的职责是单一的。
但什么是职责,以及如何判断类只有一个职责呢?职责可以认为是类负责执行的角色,也可以认为是类改变的原因。如果有多个原因要改变类,类就承担了多个职责。
以新能源汽车产品的开发迭代为例。
汽车的迭代
第一代: 以驾驶功能为主。包含:加速 , 刹车。
csharp
class Car {
// 加速
func accelerate() { }
// 刹车
func brake() { }
}
第二代:以汽车的保养为主。 包含:充电。
swift
extension Car {
/// 充电
func addCharge()
}
第三代:以汽车的娱乐为主。包含:播放音乐。
swift
extension Car {
/// 播放CD
func playCD()
/// AC控制
func turnOnAC()
}
随着业务的不断迭代,Car 必然会成为庞大的类。违反了功能设计的单一性原则。
为什么要职责分类
新能源汽车的核心之一是:电池。
swift
struct Battery {
private var charges: Int = 0
func hasCharge() -> Bool {
charges > 0
}
var isLow: Bool {
charges < 10
}
mutating func pushCharge(quantity: Int) {
charges += quantity
}
mutating func freeCharge(quantity: Int) {
charges -= quantity
}
}
如果Car
类包含了行驶、保养和娱乐职责,即职责混合在一起。修改一个职责时,可能影响另一个职责的内部实现。
scss
class Car {
var battery = Battery()
func accelerate() {
if battery.hasCharge() {
moveCar()
}
}
func addCharge() {
battery.pushCharge(quantity: 60)
}
func playCD() {
if battery.hasCharge() {
cdPlayer.play()
}
}
func turnOnAC() {
if battery.hasCharge() {
ac.turnOn()
}
}
}
电源对于多个功能都是必不可少的。accelerate()
、addCharge()
和playCD()
、turnOnAC()
方法都需要电源,对一个方法的修改可能影响其它方法。
驾驶的优先级高于娱乐,电量低时可以禁用娱乐功能。如下:
javascript
func playCD() {
guard !battery.isLow else { return }
if battery.hasCharge() {
cdPlayer.play()
}
}
耦合的职责越多,就越容易出现问题。对行驶功能的修改,可能影响到保养、娱乐功能。这些副作用是危险的,会破坏程序的稳定性。
用协议拆分职能
根据汽车的功能,拆分为三个协议:驾驶控制,汽车保养,汽车娱乐。
swift
/// 驾驶控制
protocol Drivable {
// 加速
func accelerate()
// 刹车
func brake()
}
/// 保养
protocol Maintainable {
/// 充电
func addCharge()
}
/// 娱乐
protocol Comfortable {
/// 播放CD
func playCD()
/// AC控制
func turnOnAC()
}
看似通过协议将职责进行了拆分,实则不然:
csharp
class Car: Drivable, Maintainable, Comfortable {
func accelerate() { }
func brake() { }
func addCharge() { }
func playCD() { }
func turnOnAC() { }
}
这样就又恢复到了最初的版本,Car
实现了协议的所有方法。
使用代理解决问题
将驾驶,保养,娱乐职责代理给 Driving
, Maintenance
, Comfort
。
swift
class Driving: Drivable {
private var battery = Battery()
init(battery: Battery) {
self.battery = battery
}
// 加速
func accelerate() {
guard battery.hasCharge() else { return }
// 进行加速
battery.freeCharge(quantity: 10)
}
// 刹车
func brake() { }
}
class Maintenance: Maintainable {
private var battery = Battery()
init(battery: Battery) {
self.battery = battery
}
func addCharge() {
battery.pushCharge(quantity: 60)
}
}
class Comfort: PlayCDable, TurnOnACable {
private var battery = Battery()
init(battery: Battery) {
self.battery = battery
}
func playCD() {
guard battery.hasCharge() else { return }
// 播放音乐
battery.freeCharge(quantity: 1)
}
func turnOnAC() {
guard battery.hasCharge() else { return }
// AC开启
battery.freeCharge(quantity: 3)
}
}
Car类负责组织和协调各个组件。
scss
class Car {
var battery: Battery
let driving: Driving
let maintenance: Maintenance
let comfort: Comfort
init() {
battery = Battery()
driving = Driving(battery: battery)
maintenance = Maintenance(battery: battery)
comfort = Comfort(battery: battery)
}
func accelerate() { driving.accelerate() }
func brake() { driving.brake() }
func addCharge() { maintenance.addCharge() }
func playCD() { comfort.playCD() }
func turnOnAC() { comfort.turnOnAC() }
}
在这种设计中:
Car
类负责组织和协调各个组件(Driving
,Maintenance
,Comfort
和Battery
),它不直接参与具体的功能实现,只负责通用的电池管理和各组件之间的协调。Driving
,Maintenance
,Comfort
这些组件各自负责一项或一组相关的职责,无论是驾驶功能、保养相关功能还是舒适性功能,它们都在各自的领域内保持独立和封装。Battery
作为一个独立的组件,它的职责就是管理电量相关的行为,包括充电和放电。它的状态可以由Car
类根据需要进行管理,但是具体的行为和实现都封装在Battery
类中。这样battery
只能通过暴露的接口修改。
这种设计下,每个类或者组件都有明确的职责,当某个功能需要修改或者扩展时,只需要对应的类或组件进行修改,不会影响到其他的组件,这就是单一职责原则的优点。