这是SOLID五大原则的第一篇学习笔记:单一职责原则 Single Reposibility Principle。
如果你是一名软件开发人员,并且对软件工程感兴趣,你可能已经听过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原则的第一个:单一职责。
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()
。
如果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。
参考资料: