SOLID原则
- 单一职责原则
- 开闭原则
- 里氏替换原则
- 接口隔离原则
- 依赖倒置原则
一、单一职责原则
一个类只负责一个职责
// 定义一个‘打开’协议
protocol Openable {
mutating func open()
}
// 定义一个‘关闭’协议
protocol Closeable {
mutating func close()
}
// 定义一个类 PodbayDoor 实现 Openable和Closeable 协议
class PodbayDoor: Openable, Closeable {
// 状态枚举,表示门是打开还是关闭
private enum State {
case open
case closed
}
// 初始状态为关闭
private var state: State = .closed
func open() {
state = .open
}
func close() {
state = .closed
}
}
// 定义一个类,负责打开门
final class DoorOpener {
private var door: Openable
init(door: Openable) {
self.door = door
}
func execute() {
door.open()
}
}
// 定义一个类,负责关闭门
final class DoorCloser {
private var door: Closeable
init(door: Closeable) {
self.door = door
}
func execute() {
door.close()
}
}
// 创建 PodbayDoor 实例
let door = PodbayDoor()
// 创建 DoorOpener实例 并执行打开门的操作
let doorOpener = DoorOpener(door: door)
doorOpener.execute()
// 创建 DoorCloser实例 并执行关闭门的操作
let doorCloser = DoorCloser(door: door)
doorCloser.execute()
二、开闭原则
对扩展开放,对修改封闭
/**
示例:武器库
- Shooting协议:
定义一个‘shoot’方法,所有实现该协议的类都必须提供该方法的实现;
- LaserBeam类:
实现‘Shooting’协议,并提供‘shoot’方法实现,返回激光发射的声音;
- WeaponsComposite类:
接受一个‘Shooting’类型的数组,通过构造函数注入;
shoot’方法调用数组中所有元素的‘shoot方法’,并返回结果,这使得我们可以组合多个不同的武器,并统一管理他们的行为;
- RocketLauncher类:
实现Shooting协议,并提供‘shoot’方法实现,返回火箭发射的声音;
*/
// 定义一个 Shooting 协议,包含 shoot 方法
protocol Shooting {
func shoot() -> String
}
// 实现 Shooting 协议的类 LaserBeam
final class LaserBeam: Shooting {
// 实现 shoot 方法,返回激光射击的声音
func shoot() -> String {
return "Ziiiiiip!"
}
}
// 组合多个 Shooting 实现的类 WeaponsComposite
final class WeaponsComposite {
// 持有一个 Shooting 类型的数组
let weapons: [Shooting]
init(weapons: [Shooting]) {
self.weapons = weapons
}
// 调用所有武器的 shoot 方法,并返回它们的结果
func shoot() -> [String] {
return weapons.map{ $0.shoot() }
}
}
// 创建一个 WeaponsComposite 实例,包含一个激光武器
let laser = LaserBeam()
var weapons = WeaponsComposite(weapons: [laser])
// 调用 shoot 方法, 返回 “Ziiiiiip!”
weapons.shoot()
// 实现 Shooting 协议的类 RocketLauncher
final class RocketLauncher: Shooting {
// 实现 shoot 方法,返回火箭发射的声音
func shoot() -> String {
return "Whoosh!"
}
}
// 创建一个新的 WeaponsComposite 实例,包含激光武器和火箭发射器
let rocket = RocketLauncher()
weapons = WeaponsComposite(weapons: [laser, rocket])
// 调用 shoot 方法,返回 ["Ziiiiiip!", "Whoosh!"]
weapons.shoot()
符合开放封闭原则:
- 对扩展开放:我们可以轻松添加新的武器类型(如‘RocketLauncher’),只需实现‘Shooting’协议,并将新武器实例添加到‘WeaponsComposite’中,无需修改现有代码。
- 对修改封闭:现有的 LaserBeam 和 WeaponsComposite 类不需要做任何修改,即可支持新的武器类型。这使得系统更具灵活性和可维护性。
三、里氏替换原则
子类必须能够替换其父类,并且行为一致;
通俗的来讲就是: 子类可以扩展父类的功能,但不能改变父类原有的功能;
它包含以下2层含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
- 子类中可以增加自己特有的方法;
案例一:
/**
示例:Animal and Cat
我们定义一个基类 `Animal` 和一个子类‘Cat’,并实现 `Animal` 类的抽象方法 `name()`;
其中 `Cat`能够替代 `Animal` 类并扩展功能;
`Cat`提供`run()`方法,但不会改变基类`Animal`的原有功能;
`printAnimalDetails` 函数:
接收一个 `Animal` 类型的对象,并打印其名称、吃东西和睡觉的行为。
由于 `Cat` 类实现了 `Animal` 类的所有必要方法(`name()`、`eat()`、`sleep()`),因此它可以作为 `Animal` 的替代品。
*/
// 父类
class Animal {
// 抽象方法(需要子类实现)
func name() -> String {
fatalError("Subclasses need to implement the 'name()' method")
}
// 非抽象方法
func eat() {
print("Animal eat")
}
// 非抽象方法
func sleep() {
print("Animal sleep")
}
}
// 子类
class Cat: Animal {
// 父类的抽象方法,要求子类必须实现的
override func name() -> String {
return "Cat Mii"
}
// 自己特有的方法
func run() {
print("Cat running")
}
}
使用:
// 接受一个 Animal 实例
func printAnimalDetails(animal: Animal) {
print("name \(animal.name())")
print(animal.eat())
print(animal.sleep())
}
let cat = Cat()
printAnimalDetails(animal: cat)
// 输出:
// name Cat Mii
// Animal eat
// Animal sleep
反例:
Square类继承自Rectangle类,并且假设了正方形必须有相等的宽和高;
// 矩形
class Rectangle {
var width: Double = 0.0
var height: Double = 0.0
func area() -> Double {
return width * height
}
}
// 正方形
class Square: Rectangle {
override var width: Double {
didSet {
super.height = width
}
}
override var height: Double {
didSet {
super.width = height
}
}
}
// 打印
func printArea(of shape: Rectangle) {
shape.width = 10
shape.height = 5
print("Area: \(shape.area())")
}
let rectangle = Rectangle()
print(printArea(of: rectangle)) // 输出:50.0
let square = Square()
print(printArea(of: square)) // 输出:25.0
这里违背了里氏替换原则,子类Square不能完全替代父类Rectangle; 子类Square为了使宽高相等重写了父类Rectangle的属性,导致子类Square和父类Rectangle行为不一致;
解决这类问题的最好方式即使用组合而非继承。
修改后的代码:
// 定义 Shape 协议
protocol Shape {
func area() -> Double
}
// 定义 Rectangle 类
class Rectangle: Shape {
// 宽
var width: Double
// 高
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
// 计算矩形面积
func area() -> Double {
return width * height
}
}
// 定义 Shape 类
class Square: Shape {
// 边长
var side: Double
init(side: Double) {
self.side = side
}
// 计算正方形面积
func area() -> Double {
return side * side
}
}
// 打印
func printArea(of shape: Shape) {
print("Area: \(shape.area())")
}
let rectangle = Rectangle(width: 10, height: 5)
printArea(of: rectangle) // 输出:50.0
let square = Square(side: 5)
printArea(of: square) // 输出:25.0
通过这种方式,Rectangle 和 Square 都实现了 Shape 协议,并且各自具有独立的行为,不会因为继承而导致行为不一致的问题。
四、接口隔离原则
将庞大而臃肿的接口拆分成更小更具体的接口,让类只实现它们实际需要的接口。
接口隔离原则和单一职责原则的区别在于:
- 单一职责原则注重的是类的职责,针对程序中的实现和细节;
- 接口隔离原则注重的是对接口依赖的隔离,针对抽象和程序整体框架的构建;
/**
示例:多功能打印机
假设我们有一个多功能打印机,它具有打印、扫描和传真功能。
如果我们将所有功能都放在一个接口中,这个接口可能会非常庞大,并且并不是所有实现此接口的类都需要所有功能。
然后我们定义了两种打印机:
SimplePrinter 只实现了 Printer 接口,因为它只支持打印功能。
AdvancedPrinter 实现了 Printer、Scanner 和 Fax 接口,因为它支持所有功能。
*/
// 打印
protocol Printer {
func printer(document: String)
}
// 扫描
protocol Scanner {
func scan(document: String)
}
// 传真
protocol Fax {
func fax(document: String)
}
// 打印机 - 只支持打印功能
class SimplePrinter: Printer {
func printer(document: String) {
print("Printing: \(document)")
}
}
// 打印机 - 支持打印、扫描和传真功能
class AdvancedPrinter: Printer, Scanner, Fax {
func printer(document: String) {
print("Printing: \(document)")
}
func scan(document: String) {
print("Scanning: \(document)")
}
func fax(document: String) {
print("Faxing: \(document)")
}
}
let printer = SimplePrinter()
printer.printer(document: "MySimplePrinter")
let advanced = AdvancedPrinter()
advanced.printer(document: "MyAdvancedPrinter")
advanced.scan(document: "MyAdvancedPrinter")
advanced.fax(document: "MyAdvancedPrinter")
五、依赖倒置原则
它强调:
- 高层模块不依赖于低层模块;
- 抽象不依赖于具体实现;
- 可扩展性;
简单说,依赖倒置原则倡导通过依赖接口或抽象类,而不是具体实现,从而提高代码的灵活性和可维护性。
// 示例:邮件发送系统
// 定义抽象层,协议 EmailSender 表示邮件发送的抽象
protocol EmailSender {
func sendEmail(to recipient: String, subject: String, body: String)
}
// 实现 SMTP 方式发送邮件
class SMTPSender: EmailSender {
func sendEmail(to recipient: String, subject: String, body: String) {
print("Sending email via SMTP to \(recipient) with subject \(subject) and body \(body)")
// 实际的 SMTP 发送逻辑
}
}
// 实现 API 方式发送邮件
class APISender: EmailSender {
func sendEmail(to recipient: String, subject: String, body: String) {
print("Sending email via API to \(recipient) with subject \(subject) and body \(body)")
// 实际的 API 发送逻辑
}
}
// 高层模块依赖于抽象 EmailSender 而不是具体实现
class EmailService {
private let emailSender: EmailSender
init(emailSender: EmailSender) {
self.emailSender = emailSender
}
func sendWelcomeEmail(to recipient: String) {
let subject = "Welcome!"
let body = "Welcome to our service."
emailSender.sendEmail(to: recipient, subject: subject, body: body)
}
}
// 使用 SMTP 发送邮件
let smtpSender = SMTPSender()
let emailServiceSMTP = EmailService(emailSender: smtpSender)
emailServiceSMTP.sendWelcomeEmail(to: "user@example.com")
// 使用 API 发送邮件
let apiSender = APISender()
let emailServiceAPI = EmailService(emailSender: apiSender)
emailServiceAPI.sendWelcomeEmail(to: "user@example.com")
解释:
- EmailSender 协议:
- 定义了一个抽象层,包含‘sendEmail’方法;
- 具体的邮件发送方式(SMTPSender 和 APISender):
- 这两个类分别实现了‘EmailSender’协议,提供具体的发送邮件逻辑;
- EmailService高层模块:
- 高层模块依赖于‘EmailSender’协议,而不是具体的实现,这使得高层模块于具体的实现解耦;
- 依赖注入:
- 在创建 ‘EmailService’ 实例时,将具体的 'EmailSender'实现注入;
符合依赖倒置原则:
- 高层模块不依赖低层模块: ‘EmailService’依赖于‘EmailSender’协议,而不是具体的‘SMTPSender’和‘APISender’实现;
- 抽象不依赖于具体实现: ‘EmailSender’不依赖于‘SMTPSender’和‘APISender’,而是相反;
- 可扩展性: 可以很容易的添加新的邮件发送方式,如‘MockSender’用于测试,而不需要修改‘EmailService’高层模块。