forked from pro648/tips
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
这是SOLID五大原则的第一篇学习笔记:单一职责原则 Single Reposibility Principle。 | ||
|
||
## 1. 什么是SOLID | ||
|
||
如果你是一名软件开发人员,并且对软件工程感兴趣,你可能已经听过SOLID设计原则。SOLID是编程设计五大原则首字母缩写,其目的是促进理解和开发,同时增加软件的扩展性和可维护性。 | ||
|
||
SOLID原则是由Robert C. Martin提出,每个字母对应一种原则。 | ||
|
||
- S是Single Responsibility Principle的缩写,即单一职责。 | ||
- O是Open-Closed Principle的缩写,即开闭原则。 | ||
- L是Liskov Substitution Principle的缩写,即里氏替换原则。 | ||
- I是Interface Segregation Principle的缩写,即接口隔离原则。 | ||
- D是Dependency Inversion Principle的缩写,即依赖反转原则。 | ||
|
||
在关于SOLID系列文章中,将会介绍每种原则的含义,如何实现,有什么优势,以及不遵守该原则会带来的问题。为了方便理解,介绍过程中会结合demo进行说明。 | ||
|
||
这篇文章将介绍SOLID原则的第一个:单一职责。 | ||
|
||
## 2. 单一职责 | ||
|
||
> A class should have just a unique reason to be changed, or in other words, a class should have a single responsibility. | ||
单一职责即: 一个类改变的原因只有一个,也就是类的职责是单一的。 | ||
|
||
但什么是职责,以及如何判断类只有一个职责呢?职责可以认为是类负责执行的角色,也可以认为是类改变的原因。如果有多个原因要改变类,类就承担了多个职责。 | ||
|
||
下面以汽车为例进行说明。 | ||
|
||
``` | ||
class Car { | ||
func accelerate() { } | ||
func brake() { } | ||
func turnLeft() { } | ||
func turnRight() { } | ||
func addFuel() { } | ||
func changeOil() { } | ||
func rotateTires() { } | ||
} | ||
``` | ||
|
||
这些方法都是汽车的功能,将其放到`Car`类看起来是合理的。 | ||
|
||
随着业务迭代,类会变成变成: | ||
|
||
``` | ||
class Car { | ||
... | ||
func rotateTires() { } | ||
func adjustDriverSeat() { } | ||
func turnOnAC() { } | ||
func playCD() { } | ||
} | ||
``` | ||
|
||
`Car`类违背了单一职责原则,其职责如下: | ||
|
||
- 行驶 | ||
- 保养 | ||
- 娱乐 | ||
|
||
可以通过将不同职责划分到不同类进行拆分: | ||
|
||
``` | ||
class DrivableCar { | ||
func accelerate() { } | ||
func brake() { } | ||
func turnLeft() { } | ||
func turnRight() { } | ||
} | ||
class MaintainableCar { | ||
func addFuel() { } | ||
func changeOil() { } | ||
func rotateTires() { } | ||
} | ||
class ComfortableCar { | ||
func adjustDriverSeat() { } | ||
func turnOnAC() { } | ||
func playCD() { } | ||
} | ||
``` | ||
|
||
这种划分方式会引出一个新的问题,当创建一辆车时,需要创建三个实例`DrivableCar`、`MaintainableCar`和`ComfortableCar`。 | ||
|
||
为了解决上述问题,可以通过协议来进行职责划分。 | ||
|
||
``` | ||
protocol Drivable { | ||
func accelerate() | ||
func brake() | ||
func turnLeft() | ||
func turnRight() | ||
} | ||
protocol Maintainable { | ||
func addFuel() | ||
func changeOil() | ||
func rotateTires() | ||
} | ||
protocol Comfortable { | ||
func adjustDriverSeat() | ||
func turnOnAC() | ||
func playCD() | ||
} | ||
class Car: Drivable, Maintainable, Comfortable { | ||
func accelerate() {} | ||
func brake() {} | ||
func turnLeft() {} | ||
func turnRight() {} | ||
func addFuel() {} | ||
func changeOil() {} | ||
func rotateTires() {} | ||
func adjustDriverSeat() {} | ||
func turnOnAC() {} | ||
func playCD() {} | ||
} | ||
``` | ||
|
||
但这样就又恢复到了最初的版本,`Car`实现了协议的所有方法。 | ||
|
||
使用`Car`类方式如下: | ||
|
||
``` | ||
let drivableCar: Drivable | ||
drivableCar = Car() | ||
drivableCar.accelerate() | ||
let maintainableCar: Maintainable | ||
maintainableCar = Car() | ||
maintainableCar.addFuel() | ||
let comfortableCar: Comfortable | ||
comfortableCar = Car() | ||
comfortableCar.playCD() | ||
``` | ||
|
||
这样就将`Car`的职责划分到了三个接口中,可以防止调用错误。例如,编译器会阻止以下调用`maintainableCar.accelerate()`。 | ||
|
||
## 3. 为什么要职责分离 | ||
|
||
如果`Car`类包含了行驶、保养和娱乐职责,即职责混合在一起。修改一个职责时,可能影响另一个职责的内部实现。 | ||
|
||
耦合的职责越多,就越容易出现问题。对行驶功能的修改,可能影响到保养、娱乐功能。这些副作用是危险的,会破坏程序的稳定性。 | ||
|
||
如下所示: | ||
|
||
``` | ||
class Car { | ||
var battery = Battery() | ||
func accelerate() { | ||
if battery.hasCharge() { | ||
moveCar() | ||
} | ||
} | ||
func addFuel() { | ||
battery.charge() | ||
} | ||
func playCD() { | ||
if battery.hasCharge() { | ||
cdPlayer.play() | ||
} | ||
} | ||
} | ||
``` | ||
|
||
电源对于多个功能都是必不可少的。`accelerate()`、`addFuel()`和`playCD()`方法都需要电源,对一个方法的修改可能影响其它方法。 | ||
|
||
驾驶的优先级高于娱乐,电量低时可以禁用娱乐功能。如下: | ||
|
||
``` | ||
func playCD() { | ||
guard !battery.isLow else { return } | ||
if battery.hasCharge() { | ||
cdPlayer.play() | ||
} | ||
} | ||
``` | ||
|
||
尽管使用protocol隔离了方法,`Car`类仍然实现了所有功能,使用delegate可以解决这一问题。 | ||
|
||
`Car`类遵守了`Drivable`、`Maintainable`和`Comfortable`协议,但具体实现方式是不受限的。其可以将`driving`、`maintenance`和`convenience`方法委托其它对象处理。 | ||
|
||
``` | ||
class Driving { | ||
func accelerate() {} | ||
func brake() {} | ||
func turnLeft() {} | ||
func turnRight() {} | ||
} | ||
class Maintenance { | ||
func addFuel() {} | ||
func changeOil() {} | ||
func rotateTires() {} | ||
} | ||
class Convenience { | ||
func adjustDriverSeat() {} | ||
func turnOnAC() {} | ||
func playCD() {} | ||
} | ||
class Car: Drivable, Maintainable, Comfortable { | ||
let driving = Driving() | ||
let maintenance = Maintenance() | ||
let convenience = Convenience() | ||
func accelerate() { driving.accelerate() } | ||
func brake() { driving.brake() } | ||
func turnLeft() { driving.turnLeft() } | ||
func turnRight() { driving.turnRight() } | ||
func addFuel() { maintenance.addFuel() } | ||
func changeOil() { maintenance.changeOil() } | ||
func rotateTires() { maintenance.rotateTires() } | ||
func adjustDriverSeat() { convenience.adjustDriverSeat() } | ||
func turnOnAC() { convenience.turnOnAC() } | ||
func playCD() { convenience.playCD() } | ||
} | ||
``` | ||
|
||
目前,`Car`类委托`Driving`、`Maintenance`和`Convenience`处理相关功能。`Car`类也可以维护`battery`,传递给所需类使用,这样`battery`只能通过暴露的接口修改。 | ||
|
||
## 总结 | ||
|
||
尽管单一职责理论上很简单,但不太容易付诸实践。因为有时难以确定哪些算是一种职责,以及何时一个类有了多种职责。通过不断实践,学习好的设计,可以更好的遵守Single Reposibility Principle。 | ||
|
||
参考资料: | ||
|
||
1. [Single Responsibility Principle for Class](https://clean-swift.com/single-responsibility-principle-for-class/) | ||
|
||
2. [Single Responsibility Principle in Swift](https://medium.com/movile-tech/single-responsibility-principle-in-swift-61ee11ee81b5) | ||
|
||
3. [SRP: Single Responsibility Principle](https://medium.com/swift-india/solid-principles-part-1-single-responsibility-ccfd4ff34a5a) | ||
|
||
|