在Swift 中实现流畅调用

542 阅读4分钟

在Swift 中实现流畅调用

Fluent API是一种面向对象的API风格,它通过方法链来提供一种流畅、连贯的编程体验。Fluent API通常用于构建复杂对象,简化复杂调用过程,提高代码的可读性,同时可以充分利用IDE的代码提示功能,大幅度提高编码效率。

FLuent API 实现的关键是,对象的方法调用返回对象本身,因此,可以在新返回的对象上继续实施调用,由此一气呵成,实现流畅调用。

本文介绍在 swift 语言中如何实现Fluent API。

首先定义一个示例结构。我们设想一个Employee对象,如下:

struct Employee   {
     var name :String
     var address:String 
     var age:Int
     var salary:Float
}

Employee结构有一系列属性,为简单起见,我们假设所有属性都是必须的,该结构体还有一系列的setter用来设置或者修改其属性。在没有实现Fluent调用的情况下,这些setter长得像下面一样:

mutating func setName(_ name :String)   {
        self.name = name
    }
mutating  func setAddress(_ address  :String)  {
        self.address  = address
    }
    
mutating  func setAge(_ age:Int )  {
        self.age   = age 
     }
 
mutating  func setSalary  (_ salary :Float  )   {
         self.salary = salary
    }

Swift 语言的结构是值对象,如果一个函数要改变其属性值,函数必须用mutating 关键字说明,很碍眼。

调用方式:

  var emp = Employee(name:"张三",address:"盘锦大道135号",age:31,salary:15000)
        print(emp)

//Employee(name: "张三", address: "盘锦大道135号", age: 31, salary: 15000.0)        

        emp.setAge(32)
        emp.setSalary(18000)
        print (emp)
       
//Employee(name: "张三", address: "盘锦大道135号", age: 32, salary: 18000.0)
    

下面我们将上述各个setter实现为流畅方法,实现的核心是每个setter返回对象(结构)本身,将上述Setter的返回值由Void修改为结构体本身即可,如下代码所示:

mutating func setName(_ name :String) ->Employee {
        self.name = name
        return self
}
mutating func setAddress(_ address:String)->Employee {
        self.address  = address
        return self
}
    
mutating  func setAge(_ age:Int )->Employee {
        self.age   = age 
        return self
}
 
mutating func setSalary(_ salary:Float )-> Employee {
         self.salary = salary
         return self 
}

按下述方式调用:

  var emp  = ... 
  emp.setAge(32).setSalary(18000)
      

很遗憾,上述链式调用并不能正确工作,出现如下编译错误: 在这里插入图片描述

问题在于,Employee是值对象,当我们将值对象赋值给另外一个变量是,新变量是一个不变量,不允许修改。具体到上述代码中,setSalary() 使用setAge()返回的对象,该操作缺省返回一个 不可变值对象,因此setSalary()不能够对其修改。

解决方法是,显示说明各个setter 的返回对象为var对象,在新的var对象上进行修改,然后返回该对象。

修改后的代码如下:

func setName(_ name :String) ->Employee {
       var newself = self
        newself.name = name
        return newself
}
    
func setAddress(_ address:String)->Employee {       
        var newself = self
        newself.address  = address
        return newself
}
   
func setAge(_ age:Int )->Employee  {
        var newself = self
        newself.age   = age 
        return newself
}
 
func setSalary(_ salary :Float)->Employee    {
        var newself = self
        newself.salary = salary
        return newself 
}

修改后,链式调用生效,大功告成!示例如下:

var emp  = Employee(name:"张三",address:"盘锦大道135号",age:31,salary:15000)
        print(emp)
// Employee(name: "张三", address: "盘锦大道135号", age: 31, salary: 15000.0)
   var emp1 =     emp.setAge(32).setSalary(18000)
        print (emp1)
 //Employee(name: "张三", address: "盘锦大道135号", age: 32, salary: 18000.0)
          

读者应该注意到,修改后的代码没有 mutating了,因为setter函数并没有修改对象自身的属性,而是修改一个新对象的属性,这个新对象是原对象的一个copy,所以不需要mutating了 这也算是上述方式的一个小小的红利吧。

方案改进:使用闭包去掉样本代码

虽然上述方法可以正确工作,当各个setter中存在许多完全相同的样本代码,不够优雅,存在继续优化的空间。下面我们使用闭包去掉这些样本代码。首先为Employee结构增加一个通用的修改函数 modifyprops,如下:

    //...
    private func modifyprops (_ modifier:(inout Employee) ->Void )->Employee 
    {
        var newself = self  
        modifier(&newself)
        return newself 
    }

modifyprops接受一个闭包作为参数,该闭包的类型为(inout Employee) -> Void,它接受一个 Employee结构体并对其进行修改 。 上述闭包抽象了修改过程,它集中了不变的样本代码,并将变化的代码通过闭包传递。 以 modifyprops 函数为基础,修改各setter,最终版本如下:

func setName(_ name:String) ->Employee {
        return   modifyprops  { 
        $0.name =  name 
     }  
}

func setAddress(_ address:String)->Employee  {
    return   modifyprops  { 
     $0.address =    address 
    }  
}

func setAge(_ age:Int )->Employee  {
        return   modifyprops  { 
        $0.age = age  
       }  
}

func setSalary(_ salary:Float )->Employee  {
        return modifyprops  { 
        $0.salary = salary  
       }  
}

与前一版本相比,改进后的代码没有了碍眼的样本代码,用户可以集中精力编写setter本身的逻辑。

小结

本文简要介绍了在swift语言中实现Fluent调用方法及注意事项。