SOLID 准则简介

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

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

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

接口阻隔准则

接口阻隔准则着重 客户端不应该被迫依靠于它不运用的接口。常用的术语 fat interface(即一个接口中包括太多的办法或属性)。Fat interface被认为对错内聚的接口,意味着接口提供了更多的办法、功用。Fat interface会带来与 单一责任 类似的问题,如不必要的重构,额外的测验。这是由于接口耦合了太多功用,修正一个接口,一切完成了该协议的类都需求从头构建和测验。

接口阻隔准则的重要性

遵从接口阻隔准则可以带来多个优点:

  • 增强模块的可保护性:当接口粒度适其时,保护和了解代码变得更加简单。
  • 提高代码的可重用性:更细粒度的接口更简单在不同的上下文中被重用。
  • 下降耦合度:细粒度接口下降了模块间的依靠联系,使得修正一个模块对其他模块的影响最小。

接口阻隔准则的应用

1. 别离接口

假定咱们有一个应用,它需求处理不同类型的付出方式。而不是界说一个包括一切付出办法的大接口,咱们可以为每种付出方式界说一个细粒度的协议。

// 信用卡付出
protocol CreditCardPaymentProtocol {
 func processCreditCardPayment(amount: Double)
}
​
// 微信付出
protocol WechatPaymentProtocol {
 func processWechatPayment(amount: Double)
}
​
class CreditCardPayment: CreditCardPaymentProtocol {
 func processCreditCardPayment(amount: Double) {
   // 完成 信用卡 付出逻辑
 }
}
​
class WechatPayment: WechatPaymentProtocol {
 func processWechatPayment(amount: Double) {
   // 完成 微信 付出逻辑
 }
}

2. 功用别离

考虑一个多功用打印机的比如,它可以打印、扫描和复印。而不是界说一个包括一切功用的接口,咱们可以为每项功用界说独自的接口。

protocol Printable {
 func printDocument(document: Document)
}
​
protocol Scannable {
 func scanDocument() -> Document
}
​
protocol Copiable {
 func copyDocument(document: Document) -> Document
}
​
// 别离完成每个接口
class Printer: Printable {
 func printDocument(document: Document) {
   // 打印文档
 }
}
​
class Scanner: Scannable {
 func scanDocument() -> Document {
   // 扫描文档并返回
 }
}
​
class Copier: Copiable {
 func copyDocument(document: Document) -> Document {
   // 复印文档并返回
 }
}

3. 防止不必要的依靠

经过细分接口,可以保证客户端类只依靠它们真正需求的办法,从而防止不必要的依靠。

// 数据加载
protocol DataLoading {
 func loadData()
}
​
// 数据保存
protocol DataSaving {
 func saveData()
}
​
class DataManager: DataLoading, DataSaving {
 func loadData() {
   // 加载数据
 }
​
 func saveData() {
   // 保存数据
 }
}
​
// 运用 DataManager 的类可以选择性地依靠于加载或保存功用
class ReportingTool: DataLoading {
 let dataManager: DataLoadinginit(dataManager: DataLoading) {
   self.dataManager = dataManager
 }
​
 func generateReport() {
   dataManager.loadData()
   // 生成陈述的逻辑
 }
}

ReportingTool 类专心于生成陈述的逻辑,而生成陈述只需求加载数据的功用,不需求保存数据的功用。因而,它只依靠于 DataLoading 协议,而不是直接依靠于 DataManager 类。这种规划减少了耦合,提高了代码的灵敏性和可保护性。

打破耦合,恪守接口阻隔准则

有两个类DocumentPDF

  • Document类的namecontent存储文档信息。
  • PDF类承受document入参,创立pdf文件。

这里不会重视详细完成细节,只重视接口部分。

public class Document {
 public var name: String
 public var content: String
 
 public init(name: String, content: String) {
   self.name = name
   self.content = content
 }
}
​
public class PDF {
 public var document: Document
 
 public init(document: Document) {
   self.document = document
 }
 
 public func create() -> Data {
   // do something
   return Data()
 }
}

下面声明Machine协议:

/// 机器协议
public protocol Machine {
 /// 将文档转换为PDF
 func convert(document: Document) -> PDF?
 /// 传真文档
 func fax(document: Document)
 /// 复印文档
 func copy(document: Document) -> Document?
}

FaxMachine 传真机

public class FaxMachine: Machine {
 public func convert(document: Document) -> PDF? {
   return nil
 }
 
 public func fax(document: Document) {
   print("履行传真")
 }
 
 public func copy(document: Document) -> Document? {
   return nil
 }
}

FaxMachine只有传真的功用,所以只需求完成fax(document: Document)办法,协议中的其它办法关于FaxMachine是无意义的,但由于恪守了Machine协议,其强制完成一切办法。

PhoneMachine 手机

public class PhoneMachine: Machine {
 public func convert(document: Document) -> PDF? {
   return PDF(document: document)
 }
 
 public func fax(document: Document) { }
 
 public func copy(document: Document) -> Document? {
   return nil
 }
}

PhoneMachine 可以复印 document, 或转换成 PDF。没有传真的才能,无法完成 fax(document: Document) 办法。

UltraMachine 超机

public class UltraMachine: Machine {
 public func convert(document: Document) -> PDF? {
   return PDF(document: document)
 }
 
 public func fax(document: Document) {
   print("履行传真")
 }
 
 public func copy(document: Document) -> Document? {
   // do something
   return document
 }
}

UltraMachine 是一台超强机器,可以完成协议中的三个办法。

违反接口阻隔产生的问题

产生耦合

Machine 将不同责任耦合到了一起,违反了单一责任。

由于耦合了不同责任,修正恣意办法后,其它恪守Machine协议的办法也需求从头构建和测验。

不易了解和测验

尽管上述示例没有暴露出 fat interface 的坏处:不易了解和测验,但办法变得越来越多时,这一问题会逐步明显。

可选返回值

将一切接口放入到一个协议时,由于某些类只完成部分办法,办法返回值有必要是可选类型。调用办法时,有必要处理返回值为nil的场景:

let document = Document(name: "Document Name", content: "Document Content")
let iPhone: Machine = NewIphone()
if let pdf: PDF = iPhone.convert(document: document) {
 print(pdf)
}

规划解决方案时,假如先考虑详细的完成,后规划接口,咱们会倾向于将接口放到一个协议中。假如先考虑接口规划,则会将不同接口划分到不同协议中。

遵从接口阻隔

将开始的 Machine 协议,拆分为两个协议 DocumentConverterFaxable

/// 文档转换才能
public protocol DocumentConverter {
 /// 将文档转换为PDF
 func convert(document: Document) -> PDF
 /// 复印文档
 func copy(document: Document) -> Document
}
​
/// 传真才能
public protocol Faxable {
 /// 传真文档
 func fax(document: Document)
}

防止过度规划

在接口拆分时需求找到合适状态,盲目的进行接口阻隔会导致过度规划。

当拆分接口时,可以先回答以下问题:

  • 接口阻隔是否会带来中期收益
  • 接口阻隔是否会带来长时间收益

接口拆分为上述两个协议后,类完成如下:

public class FaxMachine: Faxable {
 public func fax(document: Document) {
   print("履行传真")
 }
}
​
​
public class PhoneMachine: DocumentConverter {
 public func convert(document: Document) -> PDF {
   return PDF(document: document)
 }

 public func copy(document: Document) -> Document {
   return document
 }
}
​
​
public class UltraMachine: Faxable, DocumentConverter {
 public func convert(document: Document) -> PDF {
   return PDF(document: document)
 }
 
 public func copy(document: Document) -> Document {
   return document
 }
 
 public func fax(document: Document) {
   print("履行传真")
 }
}

运用两个协议别离完成不同责任,防止了:

  • 可选类型的问题
  • 责任耦合的问题

总结

遵从接口阻隔准则可以带来多个优点:

  • 增强模块的可保护性:当接口粒度适其时,保护和了解代码变得更加简单。
  • 提高代码的可重用性:更细粒度的接口更简单在不同的上下文中被重用。
  • 下降耦合度:细粒度接口下降了模块间的依靠联系,使得修正一个模块对其他模块的影响最小。

接口阻隔准则鼓励咱们规划精简而专心的接口,防止了过度膨胀的接口导致的不必要耦合。经过遵从接口阻隔,咱们可以构建更灵敏、易于测验和保护的Swift应用程序。在规划接口时,始终考虑运用端的需求,防止逼迫它们依靠于不需求的功用,是完成这一准则的关键。