SOLID 准则简介
SOLID 准则是五个面向对象规划的基本准则,旨在帮助开发者构建易于管理和扩展的体系。详细包括:
- 单一责任准则:一个类,一个责任。
- 开放关闭准则:对扩展开放,对修正关闭。
- 里氏替换准则:子类可替代基类。
- 接口阻隔准则:最小接口,防止不必要依靠。
- 依靠倒置准则:依靠笼统,不依靠详细。
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: DataLoading
init(dataManager: DataLoading) {
self.dataManager = dataManager
}
func generateReport() {
dataManager.loadData()
// 生成陈述的逻辑
}
}
ReportingTool
类专心于生成陈述的逻辑,而生成陈述只需求加载数据的功用,不需求保存数据的功用。因而,它只依靠于 DataLoading
协议,而不是直接依靠于 DataManager
类。这种规划减少了耦合,提高了代码的灵敏性和可保护性。
打破耦合,恪守接口阻隔准则
有两个类Document
和PDF
。
-
Document
类的name
和content
存储文档信息。 -
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
协议,拆分为两个协议 DocumentConverter
和 Faxable
。
/// 文档转换才能
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应用程序。在规划接口时,始终考虑运用端的需求,防止逼迫它们依靠于不需求的功用,是完成这一准则的关键。