利用函数组合提升代码可维护性

栏目: Swift · 发布时间: 6年前

内容简介:函数组合,在函数式编程里面也是挺重要的概念,能够将函数进行操作合并等,在有些场景下可以大幅度提升代码的可读及可维护性。下面就演示一些利用函数组合重构代码以达到更好可维护性的例子假设有如下代码:

函数组合,在函数式编程里面也是挺重要的概念,能够将函数进行操作合并等,在有些场景下可以大幅度提升代码的可读及可维护性。

下面就演示一些利用函数组合重构代码以达到更好可维护性的例子

简单场景

假设有如下代码:

process1(_ param: String) -> String
process2(_ param: String) -> String
process3(_ param: String) -> String
process4(_ param: String) -> String

这些函数来处理字符串,如果要组合调用的话,可能会写出来如下的代码:

var str = ... 
str = process1(str) 
str = process2(str) 
str = process3(str) 
str = process4(str) 
// use str

或者更洒脱一些,写出如下的代码:

let ret = process4(process3(process2(process1(str))))

第二种方式可读性不算太好,第一种方式代码写起来又会非常繁琐。那应该如何来优化呢?

优化

Swift中是支持 自定义运算符 的,而且swift中 函数是一等公民 。这两个特性是很强大的,利用他们,可以更好的实现函数的组合,可以让代码看起来更简洁、更易读。

大概的思路是把process1、process2等进行组合,组合成一个新的函数,调用这个新函数的效果,跟分开挨个调用是一样的。

优化后的代码如下:

infix operator ++ : AdditionPrecedence
func ++ (lhs: @escaping (String) -> String, rhs: @escaping (String) -> String) -> (String) -> String {
    return { rhs(lhs($0)) }
}

let ret = (process1 ++ process2 ++ process3 ++ process4)(str)

这样写出来的代码,易读且易维护,要增删操作、调整调用顺序等都是很容易的。

更多场景

上面这种场景,是比较特殊的场景,函数签名一致并且是同步函数。在真正工作中更普遍的场景是:

  1. 函数签名不一致,如process1(String),process2(Int, String)
  2. 函数是异步操作,而且回调的闭包类型也不一样等。

函数签名不一致

要能组合函数类型不一致的问题,可以参考: 利用柯里化去除重复代码 ,利用柯里化 (严格来说叫partial function application) 可以很容易解决。

代码示例如下:

process1(_ param: String) -> String
process2(_ i: Int, _ param: String) -> String
process3(_ i: Int, _ param: String) -> String
process4(_ i: Int, _ param: String) -> String

process1 ++ curry(process2) ++ curry(process3) ++ curry(process4)

不过这儿补充下,有柯里化,就有 反柯里化 。反柯里化就是给函数增加参数,让该函数跟其它函数类型对齐。

反柯里化的一种简单实现如下:

func uncurry(function: @escaping (String) -> String) -> (String, Int) -> String {
    return { s, _ in function(s) }
}

利用该反柯里化方式,新的组合代码可以适度简化为这样:

uncuryy(process1) ++ process2 ++ process3 ++ process4

uncurry完善的实现,可以参考Github上的一些实现,如 swift-overture

异步操作

再来看异步操作的问题。

说到异步处理,如果熟悉一些异步处理框架,如 PromiseKitRxSwift ,那么可能知道PromiseKit里的Promise或RxSwift里的Observable这两个对象。

仔细想想,Promise和Observable本身就是很有意思的对象,这些对象可以封装异步操作,当然,也可以表示同步操作,表示纯数据等。这些对象本身也提供了很多操作,操作之后,返回的结果仍然是该对象类型。(在函数式编程里面,这两个对象都可以理解为 Monad 对象)

理解上面这一点是关键,如果Observable本身可以封装异步操作,那么,一个异步操作就可以表达为一个同步函数,只是返回对象是一个代表同步或异步的对象。这样异步的问题就转变为同步处理的问题了。

下面继续举个简单的例子

假设有如下4个异步操作:

asyncProcess1 asyncProcess2_1 asyncProcess2_2 asyncProcess3

1、2、3这几个是并发,2_1和2_2是串行

用RxSwift写的传统代码大概如下:

asyncProcess1(param: [String]) -> Observable<Ret> {}
asyncProcess2_1(param1: Int, param2: [String]) -> Observable<Ret> {}
asyncProcess2_2(param: [String]) -> Observable<Ret> {}
asyncProcess3(param: [String]) -> Observable<Ret> {}

let process2 = Observable.concat(asyncProcess2_1(value, strs), asyncProcess2_2(strs))
let process = Observable.merge([asyncProcess1(strs), process2, asyncProcess3(strs)])
// some code

下面我们就尝试重构下该代码。

先定义下通用的concat和merge的操作符:

// 并发两个函数,合并成一个函数
infix operator ||| : RxPrecedence
// 串行两个函数,合并成一个函数
infix operator >>> : RxPrecedence

typealias RxOper<T, U> = (T) -> Observable<U>

func ||| <T, U>(lfun: @escaping RxOper<T, U>, rfun: @escaping RxOper<T, U>) -> RxOper<T, U> {
    return { value in
        Observable.merge([lfun(value), rfun(value)])
    }
}

func >>> <T, U>(lfun: @escaping RxOper<T, U>, rfun: @escaping RxOper<T, U>) -> RxOper<T, U> {
    return { value in
        lfun(value).concat(rfun(value))
    }
}

然后写相应的业务代码:

// 新的处理代码
let process = asyncProcess1 ||| (curry(asyncProcess2_1)(value) >>> asyncProcess2_2) ||| asyncProcess3
// process(strs)...

新代码的优势一目了然。并且这些例子都是拿的非常简单的示例来讲解的,真正的使用场景上,当操作数量逐渐增加,操作逻辑逐渐复杂时,传统的代码写法的冗余就越能显现。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

计算机算法

计算机算法

霍罗威茨 / 机械工业 / 2006-1 / 55.00元

本书是计算机算法在设计与分析方面的一本经典著作。书中介绍了算法和算法性能的基本知识,基本的数据结构知识,重点讨论了不同的算法设计策略,研究了下界理论等,提供了计算机算法的设计技术和有效的算法分析,以及大量的详细实例和实际应用。同时,对NP难和NP完全问题能否有效求解进行了分析。本书还汇聚了各种随机算法与并行算法的充分比较。   本书为读者提供了当前流行的对象设计语言C++的实现版本,适合作为......一起来看看 《计算机算法》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具