Functional Programming Patterns in Scala: Chatper 3

替代函数式接口

目的

将一些程序逻辑进行封装,以支持对这些逻辑的传递,以及将其存储于数据结构中,通常还可以还可以将这段封装后的逻辑作为任何其他头等的程序构造元素来使用.

概述

函数式接口是一种基本的面向对象设计模式.它由只有一个单个方法的接口构成,该方法的名称是例如 run,execute,perform,apply或一些其他常见的动词.函数式接口的实现会像任何其他方法都应遵循的那样,只执行单一明确的行为.

别名

  1. 函数对象(Function Object)
  2. Functoid
  3. 函子(Functor)

函数式替代方案

函数式接口这一面向对象世界中的模式的行为近似于函数世界中的函数.

范例:匿名函数

以匿名函数代替函数式接口中的小型实例.比如对集合中的元素进行排序,而排序的方式通常是可指定的.

为了实现这一点,需要创建一个自定义的比较器,这样一来排序算法才知道将哪个元素放在前面,Java中需要创建一个以匿名类方式实现的Comprator,而Scala中只需要一个匿名函数.

根据first-name进行排序.

Java:

Collections.sort(people, new Comparator<Person>){               // 比较逻辑简单,使用匿名类实现
    public int compare(Person p1, Pseson p2){
        return p1.getFirstName().compareTo(p2.getFirstNmae());
    }
}

Scala中创建匿名函数的语法:

(arg1: Type1, arg2: Type2) => FunctionBody

Scala:

case class Person(firstName:String, lastName:String)

val p1 = Person("Michael", "Bevilacqua") 
val p2 = Person("Pedro", "Vasquez")
val p3 = Person("Robert", "Aarons")
val people = Vector(p3, p2, p1)

people.sortWith((p1, p2) => p1.firstName < p2.firstName)

sortWith()方法期望接它的比较函数能够返回一个布尔值来帮助他判断第一个参数是否大于第二个参数.该匿名函数就是一个比较器.

范例:具名函数

对上面的例子进行扩展,首先比较first-name,相同则比较last-name,仍然相同则比较mid-name.

Java中将比较代码从匿名内部类中提取出来,并迁移到一个具名类中,Scala中将比较代码迁移到一个具名函数中.

在Java中,如果需要封装的代码逻辑比较小时,匿名类或函数是不错的选择.一旦该逻辑变得庞大,这种嵌入的逻辑会显得十分混乱.因此转为使用一个具名类:

public class ComplicatedNameComprator implements Comprator<Person>{
    public int compare(Person p1, Person p2){
        <<complicatedSortLogic>>
    }
}

Scala:

case class Person(firstName: String, middleName: String, lastName: String) 
val p1 = Person("Aaron", "Jeffrey", "Smith")
val p2 = Person("Aaron", "Bailey", "Zanthar")
val p3 = Person("Brian", "Adams", "Smith")
val people = Vector(p1, p2, p3)

def complicatedSort(p1:Person, p2:Person) = {
    if (p1.firstName != p2.firstName)
        p1.firstName < p2.firstName
    else if (p1.lastName != p2.lastName)
        p1.lastName < p2.LastName
    else 
        p1.middleName < p2.middleName
}

people.sortWith(complicatedSort)

结论

函数式接口有点奇怪,这源于Java将每个事物都转换为对象这一作风.

替代承载状态的函数式接口

目的

将一些状态与程序逻辑封装在一起,以支持对这些程序逻辑的传递,以及将其存储于数据结构当中,通常还可以将这段封装后的程序逻辑作为任何其他头等程序的构造元素来处理.

概述

上一个模式中,我们看到了如何使用高阶函数替代函数式接口,但是我们看到的实例中并没有承载任何状态.本模式中将使用闭包构造元素,以及他将如何替代需要状态的函数式接口.

别名

  1. 函数对象(Function Object)
  2. Functoid
  3. 函子(Functor)

函数式替代方案

函数式世界中的函数其实是闭包这一强大的程序构造元素的一部分.一个闭包将函数与该函数创建时的状态进行了打包.这意味着这个函数在被调用时,可以引用他在创建时所在作用域中的所有变量.

一个闭包由一个函数和该函数创建时的可用状态组成:

闭包

简单的代码,闭包

我们将创建一个由一组其他比较器组成的复合比较器,这意味着我们要将这组比较器存储在一个地方.

Java中我们将它们以入参的方式提供给自定义comprator实现的构造器,然后将它们存储在一个字段中,而Scala中只需要使用闭包.

Java:

public class ComposedComprator<T> implements Comprator<T> {
    private Comprator<T> comprators;
    public ComposedComprator(Comprator<T>..comprators){
        this.comprators = comprators;
    }
    @override
    public int compare(T o1, T o2){
        // 遍历比较器并逐个调用比较器的比较方法
    }
}

Java中创建一个名为ComposedComparator的Comprator实现,拥有一个可变长参数的构造器,接收一个比较器数组,并将他们存储在一个字段中.compare()方法调用时逐个调用数组中的比较器,返回第一个非0的结果.

Scala中实现一个名为makeComposedComparison()的高阶函数,该函数使用可变长参数作为入参,接收一个比较函数的数组,并返回一个能逐个执行这些函数的函数.

Scala与Java解决方案的另一个差别是如何返回最终的结果.Java中对比较器逐个迭代返回第一个非零结果,Scala中使用map()来对输入运行比较器,然后查找第一个非零结果.

Scala:

def makeComposedComprasion(comparsion:(Person,Person) => Int*) = {
    (p1:Person, p2:Person) =>
        comparsion.map(cmp => cmp(p1, p2)).find(_ != 0).getOrElse(0)
}

比如我们组装两个比较函数:

def firstNameComparison(p1: Person, p2: Person) = p1.firstName.compareTo(p2.firstName)
def lastNameComparison(p1: Person, p2: Person) = p1.lastName.compareTo(p2.lastName)
val firstAndLastNameComparison = makeComposedComparison( firstNameComparison, lastNameComparison )

然后进行测试:

val p1 = Person("John", "", "Adams")
val p2 = Person("John", "Quincy", "Adams")
scala> firstAndLastNameComparison(p1, p2) 
res0: Int = 0

替代命令模式