SOLID 准则简介
SOLID 准则是五个面向目标规划的根本准则,旨在协助开发者构建易于办理和扩展的系统。详细包含:
- 单一责任准则(SRP) :一个类,一个责任。
- 开放关闭准则(OCP) :对扩展开放,对修正关闭。
- 里氏替换准则(LSP) :子类可替代基类。
- 接口隔离准则(ISP) :最小接口,防止不必要依靠。
- 依靠倒置准则(DIP) :依靠抽象,不依靠详细。
Swift 编程语言中也适用这些准则,遵从这些准则,Swift 开发者能够规划出愈加灵敏、易于维护和扩展的应用程序。
里氏替换准则
氏替换准则强调子类目标应该能够替换其超类目标被运用,而不损坏程序的正确性。换句话说,程序中的目标应该能够在不改动程序希望行为的情况下,被它们的子类所替换。
示例1: 恪守LSP的规划
错误代码
有一个Shape
父类,Shape
类是抽象类,两个子类SquareShape
、CircleShape
。
class Shape { }
class SquareShape: Shape {
func drawSquare() { }
}
class CircleShape: Shape {
func drawCircle() { }
}
func draw(shape: Shape) {
if let square = shape as? SquareShape {
square.drawSquare()
} else if let circle = shape as? CircleShape {
circle.drawCircle()
}
}
另外,还有一个draw(shape:)
办法,参数为Shape
类型。在该办法中,尝试将行参转变为子类:
func draw(shape: Shape) {
if let square = shape as? SquareShape {
square.drawSquare()
} else if let circle = shape as? CircleShape {
circle.drawCircle()
}
}
let square: Shape = SquareShape()
draw(shape: square)
- 入参为子类,则制作图形。
- 入参为父类,则不做处理。
这个示例不只违反了 里氏替换 准则,也违反了 开闭准则。 假如要添加 Triangle
,就需要添加 if-case
句子,以便能够制作。
优化后代码
创建一个协议,协议声明一个公共办法draw()
,让SquareShape
和CircleShape
类恪守协议,这样子类与父类就不会有不同的行为,从而恪守了里氏替换准则。
protocol Shape {
func draw()
}
class SquareShape: Shape {
func draw() {
// draw the square
}
}
class CircleShape: Shape {
func draw() {
// draw the circle
}
}
func draw(shape: Shape) {
shape.draw()
}
这样也能够让draw(shape:)
办法对于修正关闭。当添加了新图案类型,其有必要恪守Shape
协议,从而完成draw()
办法。
public class TriangleShape: Shape {
public func draw() {
// draw the triangle
}
}
示例2:防止违反LSP的规划
Rectangle
类有两个特点 width
和 height
,一个计算面积的办法 area()
。Square
子类重写了特点set办法,以便设置一个边长的时分另一个边也同样的长度,即满足正方形四边等长。
错误代码
class Rectangle {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
func area() -> Int {
return width * height
}
}
class Square: Rectangle {
override var width: Int {
didSet {
super.height = width
}
}
override var height: Int {
didSet {
super.width = height
}
}
}
运用上述类的场景如下,值应该是25 还是 35?
let square = Square(width: 10, height: 10)
let rectangle: Rectangle = square
rectangle.height = 7
rectangle.width = 5
print(rectangle.area())
设置rectangle
目标高为7、宽为5,由于咱们不知道其真实类型为Square
,这儿预期面积为7*5 = 35。但运行得到面积为25。这儿就违反了里氏替换准则,由于Square
子类的行为与Rectangle
父类不共同。
另一个损坏里氏替换准则会来带问题的场景是开发、运用framework。当运用framework时,咱们无需、也不想了解其私有结构。当运用其公开结构时,应有共同的行为表现,而不依靠对其私有结构的了解。
优化后代码
为了恪守LSP,咱们能够运用组合而非承继,创建一个协议Geometrics
,让不同的实体有相同的行为。
protocol Geometrics {
func area() -> Int
}
public class Rectangle {
public var width: Int
public var height: Int
public init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
extension Rectangle: Geometrics {
public func area() -> Int {
return width * height
}
}
public class Square {
public var edge: Int
public init(edge: Int) {
self.edge = edge
}
}
extension Square: Geometrics {
public func area() -> Int {
return edge * edge
}
}
通常情况下,优先运用组合(composition)而非承继(inheritance),能够处理违反里氏替换准则问题。创建一个协议,让不同的实体有相同的行为,不会调用到不该运用的特点或办法。
let rectangle: Geometrics = Rectangle(width: 10, height: 10)
print(rectangle.area())
let rectangle2: Geometrics = Square(edge: 5)
print(rectangle2.area())
示例3:反常处理的补全
在这个比如中,EncryptedFileReader
承继自FileReader
偏重写了read
办法。
enum FileError: Error {
case fileNotFound
case unauthorizedAccess
case corruptedData
}
class FileReader {
func read(from path: String) throws -> String {
// 模仿读取文件,假定文件总是存在
return "File content"
}
}
class EncryptedFileReader: FileReader {
override func read(from path: String) throws -> String {
// 在读取之前进行解密处理
// 假定在某些情况下,数据可能被识别为损坏
let decrypted = "Decrypted file content"
let dataCorrupted = false // 经过某种逻辑确定
if dataCorrupted {
throw FileError.corruptedData
}
return decrypted
}
}
引进新反常类型,如EncryptedFileReader
中的FileError.corruptedData
,契合里氏替换准则(LSP)的精神,但这需要在完成和文档化方面进行仔细的办理。里氏替换准则要求子类目标能够替换父类目标,而不改动程序的正确性。这不只仅涉及到接口的共同性,也包含行为的兼容性——即子类在承继和扩展父类功用时,不该该损坏原有的契约和预期行为。
反常类型的引进与里氏替换准则
引进
EncryptedFileReader
的新反常类型是否契合里氏替换准则(LSP)取决于三个关键方面:
- 反常处理的兼容性:新反常应与父类
FileReader
的反常处理逻辑兼容,即使它是特殊的反常类型。假如它不迫使调用者改动错误处理战略,这种规划恪守了LSP。例如,corruptedData
反常假如能够在现有错误处理框架内被处理,便是兼容的。- 预期行为的坚持:新反常不该改动办法的预期行为。
EncryptedFileReader
运用者应能透明处理新反常,如同处理FileReader
的其他反常相同,不改动根本的反常处理结构。- 透明性和文档:透明性是LSP的核心,引进新反常时,应经过文档让运用者了解新反常的类型和处理办法。假如
EncryptedFileReader
文档明晰说明了这些,协助运用者正确处理反常,则遵从了LSP。假如
EncryptedFileReader
在这些方面做到了兼容性、坚持预期行为和透明性,其规划就契合LSP。这样的规划增强了代码的可维护性、可扩展性和健壮性,保证了软件组件间依靠联系的稳定性,防止了因扩展或修正导致的问题。
总结
完成里氏替换,开发者应当遵从以下辅导准则:
- 坚持接口共同性:子类应坚持与基类相同的办法签名,保证在替换时,接口的运用办法坚持不变。
- 保证成果的共同:子类办法履行的成果应与基类办法兼容,防止修正了基类预期的行为或输出格局。
- 防止子类中的强制类型转换:子类办法中防止运用对基类行为的强制类型转换,这可能会导致运行时错误。
遵从LSP能够带来以下优点:
- 提高代码的可复用性:经过承继和多态,能够运用通用的超类编写算法,然后经过不同的子类来扩展算法的行为,无需修正原有代码。
- 添加代码的可维护性:当子类替换超类时,不需修正代码,减少了因扩展和修正引进bug的危险。
- 提高代码的健壮性:保证了基类和子类之间的行为共同性,有助于防止运行时的错误。