Skip to content

Latest commit

 

History

History
246 lines (181 loc) · 6.89 KB

单一职责原则.md

File metadata and controls

246 lines (181 loc) · 6.89 KB

这是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() { }
}

这种划分方式会引出一个新的问题,当创建一辆车时,需要创建三个实例DrivableCarMaintainableCarComfortableCar

为了解决上述问题,可以通过协议来进行职责划分。

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类遵守了DrivableMaintainableComfortable协议,但具体实现方式是不受限的。其可以将drivingmaintenanceconvenience方法委托其它对象处理。

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类委托DrivingMaintenanceConvenience处理相关功能。Car类也可以维护battery,传递给所需类使用,这样battery只能通过暴露的接口修改。

总结

尽管单一职责理论上很简单,但不太容易付诸实践。因为有时难以确定哪些算是一种职责,以及何时一个类有了多种职责。通过不断实践,学习好的设计,可以更好的遵守Single Reposibility Principle。

参考资料:

  1. Single Responsibility Principle for Class

  2. Single Responsibility Principle in Swift

  3. SRP: Single Responsibility Principle