
一、单一职责原则
定义:不同的类具备不同的职责,各司其职。
1、一个类只承担一个职责,如果一个类做了多个职责,有必要的话进行分开。
2、举例说明:
(1)设计目标:设计待支付、待收货、已收货订单列表
(2)设计方案如下:
方案一:归于一个类里使用if直接进行判断,比较省事
方案二:各自分职,分成三个类
(3)问题点:方案一中如果每个功能添加新的特殊功能则就会比较麻烦,通过方案二会更好的实现各自的逻辑,更利于后期功能的更迭。
3、代码展示:
import Foundation
class OrderList: NSObject {//订单列表
var waitPayList: WaitPayList?//待支付
var waitGoodsList: WaitGoodsList?//待收货
var receivedGoodsList: ReceivedGoodsList?//已收货
}
class WaitPayList: NSObject {}
class WaitGoodsList: NSObject {}
class ReceivedGoodsList: NSObject {}
二、开闭原则
定义:软件中的对象(类、模块、函数)应该对于扩展是开放的,但是对于修改是封闭的,这意味着一个实体尽量用继承或组合的方式来扩展类的功能,在不改变它的源代码的前提下变更它的行为(如果能保证对整个架构不会产生任何影响,那就没必要搞的那么复杂)。
1、对软件实体的改动,最好用扩展而非修改的方式。
2、举例说明:
(1)设计目标:实现不同支付方式,同时易于扩展
(2)设计方案:
方案一:根据不同的支付进行判断,然后进行不同的操作
方案二:设计扩展支付方式来实现不同的支付
(3)问题点:当我们进行删除或添加一个支付方法的时候,方案一每次都要改动pay方法,可以做到,但是方案二只需要增加扩展继承PayProcessor就行了,不需要更改pay方法,更为合理一些
3、代码示例:
(1)修改之前代码
import Foundation
class PayHelper {
func pay(send: PaySendModel) -> Void {
if send.type == 0 {
//支付宝支付
} else if send.type == 1 {
//微信支付
}
}
}
class PaySendModel {
var type: Int = 0
var info: [String: AnyHashable]?
}
(2)代码修改之后
import Foundation
class PayHelper {
var processors: [Int: PayProcessor]?
func pay(send: PaySendModel) -> Void {
guard let processors = processors else {return}
guard let payProcessor: PayProcessor = processors[send.type] else {return}
payProcessor.handle(send: send)//支付
}
}
class PaySendModel {
var type: Int = 0
var info: [String: AnyHashable]?
}
protocol PayProcessor {
func handle(send: PaySendModel)
}
class AliPayProcessor: PayProcessor {
func handle(send: PaySendModel) {
}
}
class WeChatPayProcessor: PayProcessor {
func handle(send: PaySendModel) {
}
}
代码修改之后,扩展起来是不是很方便,增加支付方式只需要继承PayProcessor,不需要更改pay方法。
三、里氏替换原则
定义:一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。
1、子类可以扩展父类的方法,但不应该复写父类的方法。
2、举例说明:
(1)设计目标:设计一个汽车基类,基类里有行驶方法,现有一辆新车继承基类,添加一个获取行驶速度的方法。
(2)设计方案:
(3)问题点:
3、代码展示
(1)代码修改之前
import Foundation
class BaseCar {
func run() {
print("汽车跑起来了")
}
}
class BaoMaCar: BaseCar {
override func run() {
super.run()
print("当前行驶速度是80Km/h")
}
}
以上方法重写run方法,并且在run方法里增加汽车行驶速度的逻辑,这样是不满足的里氏替换原则的。因为所有基类Car替换成子类BaoMaCar,run方法的行为跟以前不是一模一样了。
(2)代码修改之后
import Foundation
class Car {
func run() {
print("汽车跑起来了")
}
}
class BaoMaCar: Car {
func showSpeed() {
print("当前行驶速度是80Km/h")
}
}
四、接口隔离原则
定义:一个类实现的接口中,包含了它不需要的方法。将接口拆分成更小和更具体的接口,有助于解耦,从而更容易重构、更改。
1、对象不应被强迫依赖它不使用的方法。
2、举例说明:
(1)设计目标:我们定义一个汽车接口,要求实现run等方法。
(2)设计方案:
方案一:创建车辆基类包含车辆所有功能方法
方案二:创建车辆类根据不同的车型包含不同的功能方法
(3)问题点:方案一设计比较粗暴且不易区分,因为不同的车型有着不同的功能,如果都用一个协议包含所有方法,代码会变得非常臃肿,且不易维护
3、代码示例:
(1)代码修改之前
import Foundation
protocol ICar {
func run()
func showSpeed()
func playMusic()
}
class Car: ICar {
func run() {
print("汽车跑起来了")
}
func showSpeed() {
print("当前行驶速度是80Km/h")
}
func playMusic() {
print("播放音乐")
}
}
以上代码定义Car实现了ICar的接口,但是并不是每个车都有播放音乐的功能的,这样对于一般的低端车没有这个功能,对于他们来说,这个接口的设计就是冗余的。
(2)代码修改之后
import Foundation
protocol IProfessionalCar {//具备一般功能的车
func run()
func showSpeed()
}
protocol IEntertainingCar {//具备娱乐功能的车
func run()
func showSpeed()
func playMusic()
}
class SangTaNaCar: IProfessionalCar {//桑塔纳轿车
func run() {
print("汽车跑起来了")
}
func showSpeed() {
print("当前行驶速度是80Km/h")
}
}
class BaoMaCar: IEntertainingCar {//宝马轿车
func run() {
print("汽车跑起来了")
}
func showSpeed() {
print("当前行驶速度是80Km/h")
}
func playMusic() {
print("播放音乐")
}
}
接口隔离原则的要求我们,建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。这通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
五、依赖倒置原则
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
1、面向接口编程,提取出事务的本质和共性。
2、举例说明:
(1)设计目标:我们给汽车加油,实现能够加90号的汽油,如果没有,就加93号的汽油
(2)设计方案:
方案一:创建基类,基类依赖两个底层模块实现目标
方案二:创建基类,同时创建底层模块和抽象类实现目标
(3)问题点:
3、代码示例:
(1)代码修改之前
import Foundation
class Car {
func refuel(_ gaso: Gasoline90) {
print("加90号汽油")
}
func refuel(_ gaso: Gasoline93) {
print("加93号汽油")
}
}
class Gasoline90 {
}
class Gasoline93 {
}
上面这段代码有什么问题,可以看到Car高层模块依赖了底层模块Gasoline90和Gasoline93,这样写是不符合依赖倒置原则的。
(2)代码修改之后
import Foundation
class Car {
func refuel(_ gaso: IGasoline) {
print("加\(gaso.name)汽油")
}
}
protocol IGasoline {
var name: String { get }
}
class Gasoline90: IGasoline {
var name: String = "90号"
}
class Gasoline93: IGasoline {
var name: String = "93号"
}
修改之后我们高层模块Car依赖了抽象IGasoline,底层模块Gasoline90和Gasoline93也依赖了抽象IGasoline,这种设计是符合依赖倒置原则的。
六、迪米特法则
定义:一个对象对另一个对象了解得越多,那么,它们之间的耦合性也就越强,当修改其中一个对象时,对另一个对象造成的影响也就越大。
1、一个对象应该对其他对象保持最少的了解,实现低耦合、高内聚。
2、举例说明:
(1)设计目标:实现一个给汽车加油的设计,使的我们可以随时保证加油的质量过关
(2)设计方案:
方案一:
方案二:
(3)问题点:
3、代码示例:
(1)代码修改之前
import Foundation
class Person {
var car: Car?
func refuel(_ gaso: IGasoline) {
if gaso.isQuality == true {//如果汽油质量过关,我们就给汽车加油
car?.refuel(gaso)
}
}
}
class Car {
func refuel(_ gaso: IGasoline) {
print("加\(gaso.name)汽油")
}
}
protocol IGasoline {
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90: IGasoline {
var name: String = "90号"
var isQuality: Bool = false
}
class Gasoline93: IGasoline {
var name: String = "93号"
var isQuality: Bool = true
}
可以看到上面有个问题,我们怎么知道汽油的质量是否过关呢,即时我们知道,加油判断油的质量这个事情也不应该由我们来做。
(2)代码修改之后
import Foundation
class Person {//给车加油的人
var car: Car?
func refuel(_ worker: WorkerInPetrolStation, _ gaso: IGasoline) {
guard let car = car else {return}
worker.refuel(car, gaso)
}
}
class WorkerInPetrolStation {//加油站工作人员
func refuel(_ car: Car, _ gaso: IGasoline) {
if gaso.isQuality == true {//如果汽油质量过关,我们就给汽车加油
car.refuel(gaso)
}
}
}
class Car {
func refuel(_ gaso: IGasoline) {
print("加\(gaso.name)汽油")
}
}
protocol IGasoline {
var name: String { get }
var isQuality: Bool { get }
}
class Gasoline90: IGasoline {
var name: String = "90号"
var isQuality: Bool = false
}
class Gasoline93: IGasoline {
var name: String = "93号"
var isQuality: Bool = true
}
可以看到这样我们就实现了低耦合,我们只需要知道有加油站工作人员和要加的汽油就行了,不需要知道太多汽油相关的知识,以及加油相关的操作流程,这些都交给了工作人员,这样是符合我们的迪米特原则的。
七、组合/聚合复用原则
定义:合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。
1、就是说要少用继承,多用合成关系来实现
继承复用有一定的缺点:比如如果基类发生了改变,那么派生类的的实现就不得不发生改变;而且从超类继承而来的实现是静态的,不可能在运行时发生改变,因此它的灵活性差并最终会限制复用性。而使用组合/聚合复用原则就解决了继承复用的缺点。
2、举例说明:
(1)设计目标:我们实现一个角色,可以是雇员、经理等
(2)设计方案
方案一:创建三个基类(雇员、经理、学生),角色继承自每个基类的部分属性
方案二:创建三个基类(雇员、经理、学生)和组合,组合根据需要返回所需不同的功能
(3)问题点:如果只创建一个人,那么这个是没问题的,但是我们一般不可能只用一次,如果多次使用,会多次创建相同多余代码,既不动态也不实用,方案二创建一个新的组合就解决了此类问题,如示例。
3、示例:

人被继承到雇员、学生、经理子类其中一种角色。但是实际运用中,雇员、学生和经理分别描述一种角色,而人可以同时有几种不同的角色。比如,一个人既然是经理了就一定是雇员,使用继承来实现角色,则只能使用每一个人具有一种角色,这显然是不合理的。错误的原因就是把角色的等级结构和人的等级结构混淆起来,把Has-A的关系误认为是Is-A的关系,通过下面的改正就可以正确的做到这一点。

从上图可以看出,每一个人都可以有一个以上的角色,所以一个人可以同时是雇员又是经理。从这个例子可以看出,当一个类是另一个类的角色时,不应该使用继承描述这种关系。
有了以上设计原则,可以根据设计原则实现很多好的设计模式,下一篇设计模式
网友评论