SOLID 原则简介
SOLID 原则是五个面向对象设计的基本原则,旨在帮助开发者构建易于管理和扩展的系统。具体包括:
- 单一职责原则(SRP) :一个类,一个职责。
- 开放封闭原则(OCP) :对扩展开放,对修改封闭。
- 里氏替换原则(LSP) :子类可替代基类。
- 接口隔离原则(ISP) :最小接口,避免不必要依赖。
- 依赖倒置原则(DIP) :依赖抽象,不依赖具体。
Swift 编程语言中也适用这些原则,遵循这些原则,Swift 开发者可以设计出更加灵活、易于维护和扩展的应用程序。
开放封闭原则
开闭原则指出,软件实体(如类、模块、函数等)应该对扩展开放,对修改封闭。这意味着应在不改变现有代码的前提下,扩展类的功能。在 Swift 中,这通常通过使用协议和继承来实现。
示例1: 继承示例
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
类即可,无需修改现有代码。
示例2: 协议抽象
问题代码
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
)。当我们需要同时管理篮球时,就需要修改 EquipmentRoom
类。这违反了开放封闭原则。
通过引入协议,我们可以解决这个问题。
优化后的代码
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)
}
}
通过这种方式,我们可以扩展 Ball
的功能,而无需修改 EquipmentsRoom
类。
想要扩展 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
}
}
示例3: 枚举
问题代码
下面示例中的 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)
}
}
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)
}
}
}
在这个例子中,我们使用枚举来定义不同的日志类型,并且在 LogManager
类中使用 switch-case
语句来处理不同类型的日志,这违反了开放封闭原则。
添加新的日志类型
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,需要修改多处代码。甚至会出现忘记其中一处,进入未知逻辑中。
优化后的代码
为了遵守开放封闭原则,我们可以移除枚举,并让每个日志类实现自己的日志方法。
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
}
}
遵循开闭原则的建议
- 使用协议来定义抽象,减少与具体类型的直接依赖。
- 避免使用枚举来定义多种类型的行为,而是采用协议和继承机制。
- 将类的属性设为私有,减少外部对类内部状态的直接访问。
- 避免使用全局变量,以降低代码耦合度。
通过遵循这些建议,我们可以确保我们的代码更加灵活、易于维护和扩展。