SOLID 准则简介
面向目标编程(OOP)选用“目标”作为软件规划的核心,其间目标能够包括数据(特点或字段)和操作数据的代码(办法)。SOLID 准则是五个面向目标规划的基本准则,旨在协助开发者构建易于办理和扩展的系统。Swift 编程语言中也适用这些准则,详细包括:
- 单一责任准则(SRP) :保证一个类只担任一项责任。
- 敞开关闭准则(OCP) :允许类的功用扩展,但禁止修正现有代码。
- 里氏替换准则(LSP) :子类应能无缝替换其基类。
- 接口阻隔准则(ISP) :防止逼迫客户端依靠它们不需求的接口。
- 依靠倒置准则(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
}
}
这样就能够完成关于扩展是敞开的,关于修正是关闭的。
示例三 枚举
下面示例中的 FileLogger
和 ConsoleLogger
代表两种日志类。其遵从了 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-case
,if-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
}
}
遵从开闭准则的建议
- 尽量运用协议来定义笼统,减少与详细类型的直接依靠。
- 防止运用枚举定义多种类型的行为,而是选用协议和继承机制。
- 将类的特点设为私有,减少外部对类内部状况的直接拜访。
- 防止运用全局变量,以下降代码耦合度。
将类所有特点设为私有有助于恪守开闭准则,由于它能够保证其它类不会拜访这些特点,进而保证该类的改动不会直接引起依靠该特点的类发生改变。在面向目标规划中,称之为封装。
全局变量与特点相似,运用了全局变量的模块不能被认为是关闭的。