”我更喜欢朝着正在编写的代码需求的方向去演化代码,当我去重构代码以处理耦合性、简略性以及表达性的问题时,或许会发现代码已经接近于一个特定的办法了。此刻,我把类和变量的姓名改成运用办法的姓名,并且把代码的结构更改为以更正规的办法的办法。这样,代码就回归为办法“
— 《敏捷软件开发》
不要拘泥于办法,遇到什么情况,就用最适合的办法去写代码。以下许多案例代码没有对错之分,仅仅用来举例,供给一些基本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大准则