”我更喜欢朝着正在编写的代码需求的方向去演化代码,当我去重构代码以处理耦合性、简略性以及表达性的问题时,或许会发现代码已经接近于一个特定的办法了。此刻,我把类和变量的姓名改成运用办法的姓名,并且把代码的结构更改为以更正规的办法的办法。这样,代码就回归为办法

— 《敏捷软件开发》

不要拘泥于办法,遇到什么情况,就用最适合的办法去写代码。以下许多案例代码没有对错之分,仅仅用来举例,供给一些基本tips, 给咱们多一些思路,也给自己做一份记录见证自己的生长。

常见问题

咱们平时常常运用if else/switch case,开始或许只要两三个分支,可是到后期保护的时分常常会出现超多分支,这时圈复杂度也涨上来了。比方:

func complexLogic(params) {
if (condition1) {
   logic1() 
} else if (condition1) {
   logic2()    
} else if (condition1) {
logic3()
}
....
else {
logicn()    
}
}

这种办法一不小心便是代码劣化的根源。会出现的几个问题:

  • 1 办法变得超级长。假如将分支逻辑抽离成类办法,那么类也会变得超级长,会影响阅读。
  • 2 这么多分支逻辑的复用性不好,只能在类内部运用。并且直接引证类,也会形成不必要的依靠。
  • 3 在enum的case比较多的的时分,会导致switch case膨胀。
  • 4 还有一个便是当开发意识懈怠的时分、偷懒、图便利让支逻辑相互耦合。

运用战略办法

看上面的代码,发现他们都有许多相似的当地,都是满意一个条件后,处理一个特定逻辑。将代码修正一下:

#1 接口笼统
protocol ILogic {
    func execute(params)
}
class Client {
    func complexLogic(logic: ILogic) { 
logic.execute(params)
}
...
}
#2 算法完成
struct Logic1: ILogic {
    func execute(params) {
    ... // execute logic
}
}
struct Logic2: ILogic {
    func execute(params) {
    ... // execute logic
}
}
struct Logic3: ILogic {
    func execute(params) {
    ... // execute logic
}
}
# 3 运用
let client = Client()
let logic1 = Logic1()
let logic2 = Logic2()
let logic3 = Logic3()
// 这儿举例许多人或许会有疑问,假如要运用还不是要用ifelse区别?其实不用,在每个调用的当地就能够确定需求哪种类型了, 假如的确不适用,也没必要强行运用这种办法。
client.complexLogic(logic1)
...
client.complexLogic(logic2)
...
client.complexLogic(logic3)

上述代码能够看出,三种算法都独立出来,不会相互污染,也能够供给给其他当地复用。

很显然,这儿代码量变大了许多,里边就多出了许多类,仍是要看详细场景,主张假如if else比较复杂的时分能够这样处理。

战略办法的界说

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

界说一组算法,将每个算法都封装起来,并且使他们之间能够交换。战略办法使算法能够独立于客户端而变化。

上述示例代码便是将if else中各分支逻辑当成独立算法,然后再设置到客户度中。每个算法是独立保护的。

这样契合几个准则:

  • SRP(责任单一):各算法各司其职,各自保护
  • OCP(开闭准则):对修正封闭,对扩展开放。上述示例非常容易扩展
  • DIP(依靠倒置准则):关于客户端来说,它是依靠笼统的,而不是依靠完成。

当然关于 if else 的免除还有许多争议,可是全部不要为了运用而运用,而不去考虑当时场景。比方if else 有一个很好的优势便是进步程序的效率,比方将命中率较高的算法放在if else的最前面。

战略办法也是一种依靠注入,到达依靠翻转的目的(能够自己画一个UML图就知道为何会依靠翻转了)。

常见用法举例

与工厂办法结合

上述的if else 免除后,在有些情况下也需求指定算法,有些事详细业务运用一个算法,有些的确需求用多个算法,这时就能够运用工厂办法将这些算法映射过去

class LogicFactory {
    let logics: [String: ILogic] = [
    "logic1": Logic1(),
    "logic2": Logic2(),
    "logic3": Logic3(),
]
// 这儿的type最好运用enum ,这儿为了便利运用String,Client初始化办法自行补充
func createLogicClient(type: String) -> Client {
    return Client(logic: logics[type])
}
}

其实我感觉这也是if else的一种变体,由于if else 本质上也是一种映射联系,只不过读取字典更快,当然仍是要着重,坚持代码的简略才是最佳的办法,由于你不知道后期会有什么改动,只要坚持简略后期保护起来就简略,就更易于变更。

switch case 的拆解

先看实例:

enum ButtonType {
    case image
case video
case hashtag
case music
var title: String {
    switch self {
    case .image:
return "Image"
case .video:
return "Video"
case .hashtag:
return "Hashtag"
case .music:
return "Music"
}
}
var normalIcon: UIImage {
    switch self {
    case .image:
return Assets.image1
case .video:
return Assets.image2
case .hashtag:
return Assets.image3
case .music:
return Assets.image4
}
}
var selectedIcon: UIImage {
    switch self {
    case .image:
return Assets.simage1
case .video:
return Assets.simage2
case .hashtag:
return Assets.simage3
case .music:
return Assets.simage4
}
}
}

这是一段工具栏的案例代码,ButtonType作为一种类型载体,包含了许多类型数据,这种使咱们用enum的常用办法。很显然,ButtonType的case许多的时分,每新增一个只读熟悉就爆破了,需求写许多switch-case。再者,假如需求的只读属性许多,那么便是两层爆破,这个enum会长的吓人,一旦要新增一个case,那改动的当地就超级多了。

注意:这儿仅仅拿上述代码举例,并不是说上述写法一定有问题,全部看场景。

这儿还有很明显的问题,每个ButtonType都有一组呼应类型,比方正常点击呼应,不可用状态的点击呼应等等,在这些点击呼应的当地也会需求switch case进行处理。

那么怎么处理这个问题呢?便是运用战略,看下列代码:

portocol ButtonType {
    var title: String { get }
var normalIcon: UIImage { get }
var selectedIcon: UIImage { get }
func normalAction()
func unableAction()
}
struct ImageButton: ButtonType {
    var title: String { return "Image" }
var normalIcon: UIImage { return Asset.image1 }
var selectedIcon: UIImage { return Asset.simage1 }
func normalAction() {
    ... // do something when clicked in normal state
}
func unableAction() {
    ... // do something when clicked in unable state
}
}
struct MusicButton: ButtonType {
    var title: String { return "Image" }
var normalIcon: UIImage { return Asset.image1 }
var selectedIcon: UIImage { return Asset.simage1 }
func normalAction() {
    ... // do something when clicked in normal state
}
func unableAction() {
    ... // do something when clicked in unable state
}
private weak var delegate: SomeDelegate?
init(delegate: SomeDelegate) { //#1
    self.delegate = delegate
}
}

看上述将switch case 拆解为详细的”算法“,每个算法独立保护,假如需求新增算法,直接承继ButtonType接口即可,然后修正一下客户端(运用新增算法的当地)即可。这儿很明显契合OCP,运用这种办法后会发现代码中从上到下,几乎很少有enum的switch case,代码逻辑很变的很简练。

当然这也会有一个问题,便是结构体会增加许多,在前期开发时的确不如直接运用switch case便利,可是后期会有巨大优势。仍是那句话,详细场景详细措施,咱们的观念要随上下文去变动。

这儿有个小Tips。或许许多人会觉得,那假如这些算法需求调用其他逻辑怎么办?很简略,看#1处的初始化代码,需求什么依靠,直接传入即可。

小结

本节讲解了开发过程中战略办法的部分详细运用场景,代码场景千千万,是无法穷尽的,可是只要紧记规划办法6大准则,在开发的时分进行权衡取舍,你也能够写出自己的规划办法。

拓展

  • 战略办法
  • 规划办法6大准则