SOLID 准则简介

SOLID 准则是五个面向对象规划的基本准则,旨在协助开发者构建易于办理和扩展的体系。详细包含:

  1. 单一责任准则(SRP) :一个类,一个责任。
  2. 敞开关闭准则(OCP) :对扩展敞开,对修正关闭。
  3. 里氏替换准则(LSP) :子类可替代基类。
  4. 接口阻隔准则(ISP) :最小接口,防止不必要依靠。
  5. 依靠倒置准则(DIP) :依靠笼统,不依靠详细。

Swift 编程语言中也适用这些准则,遵循这些准则,Swift 开发者可以规划出愈加灵活、易于保护和扩展的运用程序。

依靠回转准则

依靠回转准则的意图是减少高层模块与低层模块之间的直接依靠,经过笼统使得依靠关系从详细完成细节中解耦。

两个主要指导方针:

  1. 高层模块不该直接依靠低层模块,两者都应依靠于笼统。
  2. 笼统不该依靠细节,细节应依靠笼统。

依靠回转准则经过将关注点从详细完成转移到笼统层面,进步了体系的模块化和灵活性,降低了模块间的耦合度,使软件更易保护和扩展。

示例1:信息处理中心

一个运用程序需求处理各种类型的音讯,如电子邮件、SMS、推送告诉等。

错误代码

在传统的规划中,咱们或许会有一个MessageService类,它直接依靠于详细的发送方法完成,如EmailSenderSmsSender 等类。

// 高层模块
class MessageService {
 private let emailSender = EmailSender()
 private let smsSender = SmsSender()
 // ...更多的发送器func sendEmail(message: String) {
   emailSender.send(message: message)
 }
​
 func sendSMS(message: String) {
   smsSender.send(message: message)
 }
​
 // ...更多的发送办法
}
​
// 低层模块
class EmailSender {
 func send(message: String) {
   // 发送电子邮件逻辑
 }
}
​
class SmsSender {
 func send(message: String) {
   // 发送SMS逻辑
 }
}

这种规划直接将高层的MessageService与低层的发送器完成绑定在一起,当需求添加新的音讯发送方法或者修正现有的发送逻辑时,都需求修正MessageService类,违背了开闭准则(OCP)。

优化后代码

运用依靠回转准则,咱们首要界说一个笼统的音讯发送接口(笼统层),然后让一切的发送器完成这个接口。MessageService依靠于这个接口,而不是详细的发送器完成。

// 笼统层
protocol MessageSender {
 func send(message: String)
}
​
// 高层模块
class MessageService {
 private let sender: MessageSenderinit(sender: MessageSender) {
   self.sender = sender
 }
​
 func sendMessage(message: String) {
   sender.send(message: message)
 }
}
​
// 低层模块完成笼统层
class EmailSender: MessageSender {
 func send(message: String) {
   // 发送电子邮件逻辑
 }
}
​
class SmsSender: MessageSender {
 func send(message: String) {
   // 发送SMS逻辑
 }
}
​
// 运用依靠注入完成依靠回转
let emailService = MessageService(sender: EmailSender())
let smsService = MessageService(sender: SmsSender())

经过依靠回转,MessageService不再直接依靠于详细的发送器完成,而是依靠于MessageSender接口,完成了高度的模块化和可扩展性。

这样,当咱们需求引进新的音讯发送方法时,只需添加一个新的MessageSender完成即可,无需修正MessageService类,完成了高度的模块化和可扩展性。

示例2: 设置办理模块

让咱们考虑一个违背依靠回转准则(DIP)的比如,并展示如何经过运用DIP来优化它。这个比如会根据一个简化的移动运用中的设置办理模块,这个模块负责存取用户的偏好设置。

错误代码

假设咱们在一个移动运用中有一个设置页面,其间直接运用了详细的存储机制(如UserDefaults在iOS中)来保存用户的偏好设置。

class SettingsViewController {
 func toggleDarkMode(isEnabled: Bool) {
   UserDefaults.standard.set(isEnabled, forKey: "darkModeEnabled")
 }
 
 func isDarkModeEnabled() -> Bool {
   return UserDefaults.standard.bool(forKey: "darkModeEnabled")
 }
}

在这个比如中,SettingsViewController直接依靠于UserDefaults,这是iOS平台上的一个详细的偏好设置存储机制。

这种直接依靠导致了几个问题:

  • 难以测验:在单元测验中替换或模仿UserDefaults会比较麻烦。
  • 耦合性高:如果决议替换另一种存储机制(例如存储到云端),将需求重写SettingsViewController中的相关代码。
  • 违背了DIP:高层模块(SettingsViewController)直接依靠于低层模块(UserDefaults)的详细完成,而不是依靠于笼统。

优化后代码

运用依靠回转准则,界说一个偏好设置存储的笼统接口,并让SettingsViewController依靠这个接口,而不是详细的完成。然后,咱们可以创立UserDefaults的一个包装器,完成这个接口。

// 笼统层
protocol SettingsStorage {
 func set(_ value: Bool, forKey key: String)
 func bool(forKey key: String) -> Bool
}
​
// 低层模块完成笼统层
class UserDefaultsStorage: SettingsStorage {
 func set(_ value: Bool, forKey key: String) {
   UserDefaults.standard.set(value, forKey: key)
 }
 
 func bool(forKey key: String) -> Bool {
   return UserDefaults.standard.bool(forKey: key)
 }
}
​
// 高层模块依靠笼统层
class SettingsViewController {
 private let storage: SettingsStorage
 
 init(storage: SettingsStorage) {
   self.storage = storage
 }
 
 func toggleDarkMode(isEnabled: Bool) {
   storage.set(isEnabled, forKey: "darkModeEnabled")
 }
 
 func isDarkModeEnabled() -> Bool {
   return storage.bool(forKey: "darkModeEnabled")
 }
}
​
// 运用依靠注入完成依靠回转
let settingsViewController = SettingsViewController(storage: UserDefaultsStorage())

经过这种方法,SettingsViewController现在依靠于SettingsStorage接口,而不是详细的UserDefaults完成。这样做的优点包含:

  • 进步了可测验性:可以轻松地为SettingsStorage接口供给一个模仿完成来进行单元测验。
  • 降低了耦合性:如果需求替换存储机制,只需供给SettingsStorage的另一个完成即可,无需修正SettingsViewController的代码。
  • 契合DIPSettingsViewController(高层模块)现在依靠于笼统(SettingsStorage接口),而不是详细的完成(UserDefaults),契合了依靠回转准则。

这个优化示例展示了如何经过运用依靠回转准则来进步代码的可保护性、可扩展性和可测验性。

示例3:移动端网络恳求模块的笼统化

在移动运用开发中,与服务器的通信是一个常见需求。不同的运用或许会运用不同的网络恳求库或结构,如iOS中的Alamofire。如果直接在事务逻辑中硬编码这些库的运用,那么在替换库或修正恳求逻辑时将会非常困难。

错误代码

在不运用依靠回转的情况下,一个iOS的网络服务或许直接在事务层中运用Alamofire进行网络恳求。

import Alamofire
class UserService {
 func fetchUserDetails(userID: String, completion: @escaping (User?, Error?) -> Void) {
   let url = "https://example.com/api/user/(userID)"
   Alamofire.request(url, method: .get).responseJSON { response in
     // 解析呼应并回调
   }
 }
}

这种方法直接将网络恳求库Alamofire与事务逻辑耦合在一起,不便于保护和测验。

优化后代码

为了运用依靠回转准则,咱们首要界说一个网络恳求的笼统接口,然后让详细的网络恳求库完成这个接口。事务逻辑将依靠于这个接口,而不是详细的完成。

// 笼统层
protocol NetworkService {
 func request(_ url: String, method: HttpMethod, completion: @escaping (Result<Data, Error>) -> Void)
}
​
// 高层模块
class UserService {
 private let networkService: NetworkServiceinit(networkService: NetworkService) {
   self.networkService = networkService
 }
​
 func fetchUserDetails(userID: String, completion: @escaping (Result<User, Error>) -> Void) {
   let url = "https://example.com/api/user/(userID)"
   networkService.request(url, method: .get) { result in
     switch result {
     case .success(let data):
       // 解析数据并回调
       let user = parseUserData(data)
       completion(.success(user))
     case .failure(let error):
       completion(.failure(error))
     }
   }
 }
}
​
// 低层模块完成笼统层
class AlamofireNetworkService: NetworkService {
 func request(_ url: String, method: HttpMethod, completion: @escaping (Result<Data, Error>) -> Void) {
   // 运用Alamofire发送恳求并处理呼应
 }
}
​
// 运用依靠注入完成依靠回转
let userService = UserService(networkService: AlamofireNetworkService())

在这个比如中,经过引进NetworkService接口,UserService变得与详细的网络恳求库(如Alamofire)解耦。这样一来,如果需求替换网络恳求库,只需供给一个新的NetworkService完成即可,而无需修正UserService的代码。

依靠回转准则的实践与本钱

依靠回转准则在软件规划中扮演着重要人物,它经过引进更多的笼统和服务,添加了体系的复杂性。

  • 开发复杂性添加:为了遵守DIP,你需求界说接口或笼统类并完成它们。
  • 功用考量:尽管现代编程语言和编译器的优化一般使得经过接口调用引进的功用开支微乎其微,但在极点功用敏感的运用中,这种额定的笼统层或许仍是需求被考虑。
  • 测验复杂性:尽管依靠回转有助于进步代码的可测验性,但是完成和办理这些测验本身也需求额定的尽力和时刻。
  • 学习曲线:关于团队成员,了解和正确完成依靠回转或许需求一段时刻。

但是,这种方法实际上带来了更好的模块化、灵活性和可扩展性。

尽管初看或许会觉得办理多个服务愈加困难,但实际上,依靠回转以及依靠注入等技术可以有效地解决这些潜在的办理问题。

以下是几个要害点,解说了为什么这种做法实际上有助于改进办理和保护:

以下是几个要害点,解说了为什么这种做法实际上有助于改进办理和保护:

  • 解耦合增强灵活性:经过模块化规划,将详细完成与高层战略别离,体系的各个部分变得愈加独立。这种独立性答应开发者单独修正或扩展特定功用,而不会影响到体系的其他部分。
  • 便于保护和扩展:体系规划应该是敞开的关于扩展,但是关闭的关于修正。依靠回转准则正是遵循了这一点,使得添加新功用或服务不需求修正现有的代码,只需求添加新的完成即可。
  • 进步测验性:因为高层模块不直接依靠于详细完成,这使得进行单元测验变得愈加简单。
  • 战略形式和工厂形式:依靠回转准则常常与战略形式和工厂形式结合运用,这不仅进步了代码的可重用性,还增强了体系的灵活性。

总结来说,尽管初期完成依靠回转或许会添加体系的笼统层级和看似添加办理上的复杂性,但长远来看,它实际上供给了更好的别离关注点、增强了体系的可保护性和可扩展性。经过利用依靠注入结构和合理的规划形式,可以有效地办理这些笼统,保证体系既灵活又强健。