iOS APP的SOLID原则(Swift)

SOLID原则

  1. 单一职责原则
  2. 开闭原则
  3. 里氏替换原则
  4. 接口隔离原则
  5. 依赖倒置原则

一、单一职责原则

一个类只负责一个职责

// 定义一个‘打开’协议
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()

符合开放封闭原则

  1. 对扩展开放:我们可以轻松添加新的武器类型(如‘RocketLauncher’),只需实现‘Shooting’协议,并将新武器实例添加到‘WeaponsComposite’中,无需修改现有代码。
  2. 对修改封闭:现有的 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'实现注入;

符合依赖倒置原则

  1. 高层模块不依赖低层模块:
    ‘EmailService’依赖于‘EmailSender’协议,而不是具体的‘SMTPSender’和‘APISender’实现;
  2. 抽象不依赖于具体实现:
    ‘EmailSender’不依赖于‘SMTPSender’和‘APISender’,而是相反;
  3. 可扩展性:
    可以很容易的添加新的邮件发送方式,如‘MockSender’用于测试,而不需要修改‘EmailService’高层模块。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容