SOLID 准则简介

面向目标编程(OOP)选用“目标”作为软件规划的核心,其间目标能够包括数据(特点或字段)和操作数据的代码(办法)。SOLID 准则是五个面向目标规划的基本准则,旨在协助开发者构建易于办理和扩展的系统。Swift 编程语言中也适用这些准则,详细包括:

  1. 单一责任准则(SRP) :保证一个类只担任一项责任。
  2. 敞开关闭准则(OCP) :允许类的功用扩展,但禁止修正现有代码。
  3. 里氏替换准则(LSP) :子类应能无缝替换其基类。
  4. 接口阻隔准则(ISP) :防止逼迫客户端依靠它们不需求的接口。
  5. 依靠倒置准则(DIP) :高层模块不应依靠低层模块,二者都应依靠于笼统。

遵从这些准则,Swift 开发者能够规划出更加灵活、易于保护和扩展的应用程序。

开闭准则

开闭准则指出,软件实体(如类、模块、函数等)应该对扩展敞开,对修正关闭。这意味着应在不改动现有代码的前提下,扩展类的功用。在 Swift 中,这一般经过运用协议和继承来完成。

示例一 继承示例

class Payment {
 func processPayment(amount: Double) {}
}
​
class CreditCardPayment: Payment {
 override func processPayment(amount: Double) {
   print("Processing credit card payment")
 }
}
​
class PayPalPayment: Payment {
 override func processPayment(amount: Double) {
   print("Processing PayPal payment")
 }
}

若要增加新的支付办法(如ApplePayPayment),只需继承Payment类即可,无需修正现有代码。

示例二 协议笼统

struct Ball {
 let name: String
 let age: Int
 
 init(name: String, age: Int) {
   self.name = name
   self.age = age
 }
}
​
struct EquipmentRoom {
 var balls: [Ball]
 init(balls: [Ball]) {
   self.balls = balls
 }
 
 mutating func add(ball: Ball) {
   balls.append(ball)
 }
}

球类器材房(EquipmentRoom),办理的是球(Ball)。当需求一起办理篮球的时候,就需求修正 BallsRoom 类。因此,没有恪守开闭准则。

经过修正 Ball 类为新的 Equipment ,来满意需求功用,这样 EquipmentRoom 就不需求修正了。但是 Ball 类有可能在多处运用,修正可能导致其发生新的问题。

在一个大型工程中,对确定模块的修正不应影响或强制其他模块进行更改。

遵从开闭准则

经过协议解决问题。

protocol EquipmentType {
 init(name: String, age: Int)
}
​
​
struct Ball: EquipmentType {
 
 let name: String
 let age: Int
 
 init(name: String, age: Int) {
   self.name = name
   self.age = age
 }
}
​
​
struct EquipmentsRoom {
 var equipments: [EquipmentType]
 init(equipments: [EquipmentType]) {
   self.equipments = equipments
 }
 
 mutating func add(equipment: EquipmentType) {
   equipments.append(equipment)
 }
}

创立EquipmentType 协议,Ball 类遵从该协议。EquipmentsRoom 不再依靠Ball ,转而依靠 EquipmentType 。想要扩展 Ball的功用,只需求新创立一个实体,遵从 EquipmentType 即可。

struct Basketball: EquipmentType {
 let name: String
 let age: Int
 
 init(name: String, age: Int) {
   self.name = name
   self.age = age
 }
 
 func otherMethod() {
   // something
 }
}

这样就能够完成关于扩展是敞开的,关于修正是关闭的。

示例三 枚举

下面示例中的 FileLoggerConsoleLogger 代表两种日志类。其遵从了 Logger 协议。每个类包括 LoggerType 特点。

enum LoggerType {
 case file
 case console
}
​
​
protocol Logger {
 var type: LoggerType { get }
}
​
class FileLogger: Logger {
 let type: LoggerType = .file
 func fileLog(_ message: String) {
   print(message)
 }
}
​
class ConsoleLogger: Logger {
 let type: LoggerType = .console
 func consoleLog(_ message: String) {
   print(message)
 }
}
​

LogManager 类担任接口并执行,以便输出对应的日志信息。

// 日志办理器类担任接纳恣意Logger实例并调用它的log办法
class LogManager {
 func log(message: String, using logger: Logger) {
   switch logger.type {
   case .file:
     (logger as? FileLogger)?.fileLog(message)
   case .console:
     (logger as? ConsoleLogger)?.consoleLog(message)
 }
}

如果要增加一种新的类型 RemoteLogger

class RemoteLogger: Logger {
 let type: LoggerType = .remote 
 func remoteLogPrint(_ message: String) {
   print(message)
 }
}

还需求修正 LogManager 类的 func log(message: String, using logger: Logger) 办法处理相应的类型。

class LogManager {
 func log(message: String, using logger: Logger) {
   switch logger.type {
   case .file:
     (logger as? FileLogger)?.fileLogPrint(message)
   case .console:
     (logger as? ConsoleLogger)?.consoleLogPrint(message)
   case .remote:
     (logger as? RemoteLogger)?.remoteLogPrint(message)
   }
 }
}

这样没有满意 对扩展敞开,对修正关闭.

枚举(enum)的运用可能导致代码违反开闭准则,由于增加新的枚举值一般需求修正现有的switch-case逻辑。为防止这一问题,能够将详细行为封装在遵从一起协议的类中,而非直接依靠于枚举类型。

此外还需求注意的是:运用 enum 子孙代码需求判断多种状况,会存在许多 switch-caseif-else 代码的状况。意味着创立一个枚举case,需求修正多处代码。甚至会呈现忘记其间一处,进入不知道逻辑中。

遵从开闭准则

移除enum,每个实体完成本身的func log(message: String)办法,这样恪守开闭准则。如下所示:

protocol Logger {
 func log(message: String)
}
​
class FileLogger: Logger {
 func log(message: String) {
   // do something
 }
}
​
class ConsoleLogger: Logger {
 func log(message: String) {
   // do something
 }
}
​
class LogManager {
 func log(message: String, using logger: Logger) {
   logger.log(message: message)
 }
}

如果需求新增类型,只需求创立一个新的实体,无需修正 LogManager

class RemoteLogger: Logger {
 func log(message: String) {
   // do something
 }
}

遵从开闭准则的建议

  • 尽量运用协议来定义笼统,减少与详细类型的直接依靠。
  • 防止运用枚举定义多种类型的行为,而是选用协议和继承机制。
  • 将类的特点设为私有,减少外部对类内部状况的直接拜访。
  • 防止运用全局变量,以下降代码耦合度。

将类所有特点设为私有有助于恪守开闭准则,由于它能够保证其它类不会拜访这些特点,进而保证该类的改动不会直接引起依靠该特点的类发生改变。在面向目标规划中,称之为封装。

全局变量与特点相似,运用了全局变量的模块不能被认为是关闭的。