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层含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
- 子类中可以增加自己特有的方法;
/**
示例:图形 和 圆形
我们将定义一个基类‘Shape’和一个子类‘Circle’,其中‘Circle’能够替代‘Shape’类并扩展功能。
‘Circle’将提供计算圆面积的方法,但不会改变基类‘Shape’的原有功能
*/
// 定义基类 Shape
class Shape {
// 获取描述(非抽象方法)
func description() -> String {
return "This is a shape."
}
// 计算面积(抽象方法)
func area() -> Double {
fatalError("Subclasses need to implement the 'area()' method")
}
}
// 定义子类 Circle
class Circle: Shape {
var radius: Double
init(radius: Double) {
self.radius = radius
}
// 实现area方法,计算圆面积
override func area() -> Double {
return .pi * radius * radius
}
// 增加Circle特有的方法
func diameter() -> Double {
return 2 * radius
}
}
// 使用
func printShapeDetails(shape: Shape) {
print(shape.description())
print("Area:\(shape.area())")
}
let circle = Circle(radius: 5)
// 将 Circle 实例传递给接受 Shape 的函数
printShapeDetails(shape: circle)
// 调用 Circle 的特有方法
print("Diameter: \(circle.diameter())")
四、接口隔离原则
将庞大而臃肿的接口拆分成更小更具体的接口,让类只实现它们实际需要的接口。
接口隔离原则和单一职责原则的区别在于:
- 单一职责原则注重的是类的职责,针对程序中的实现和细节;
- 接口隔离原则注重的是对接口依赖的隔离,针对抽象和程序整体框架的构建;
/**
示例:多功能打印机
假设我们有一个多功能打印机,它具有打印、扫描和传真功能。
如果我们将所有功能都放在一个接口中,这个接口可能会非常庞大,并且并不是所有实现此接口的类都需要所有功能。
然后我们定义了两种打印机:
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 发送逻辑
}
}
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’高层模块。