`

记Protocol Oriented Programming in Swift of WWDC 2015

阅读更多
其实最先朋友让我就这个题目写篇文章的时候,我是拒绝的,因为觉得苹果就是在炒冷饭, 把已经流行了数十年的OOP中的“面向接口编程”还拿来讲,看完整个Session之后呢,虽然还是觉得在炒冷饭,但是毕竟还是加了蛋的,有些东西还是值得说说的。

通常谈到面向接口编程,其主要作用是把系统设计和具体实现分离开,让系统的每个部分都可以在不影响别的部分的情况下,改变自身的具体实现。接口的设计就反映了系统设计人员对系统的抽象理解。

而苹果在除了把Interface名字换为Protocol之外,还在这个理念上添加了怎样的鸡蛋呢?

Self Requirement

第一个蛋:学名叫做Self Requirement,主要的作用就是解决使用Protocol(接口)之后,类型缺失的问题,即,当在对象类型上使用了protocol类型之后,如果还需要调用对象自有的一些方法,那么就不得不用as!做一个强制类型转换。

Talk is cheap, Let's show the code

假设我们要实现一个通用的二分查找函数,如下:
protocol Ordered {
    func precedes(other: Ordered) -> Bool
}

func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
    var lo = 0, hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}
任何实现了Ordered protocol的对象(包括class,struct和enum)都可以使用该函数做二分查找。下面我们就来看看一个实现了Ordered协议Number类的代码:

class Number : Ordered {
  var value: Double = 0
  func precedes(other: Ordered) -> Bool {
        return value < (other as! Number).value // Warning!!! as!的使用就是丢失类型信息的信号。
  }
}
而通过Self requirement特性,可以在protocol中使用Self来指代实现这个接口的具体类,如下:
protocol Ordered {
    func precedes(other: Self) -> Bool
}
在具体的实现类中,就可以像下面这样实现:

class Number : Ordered {
  var value: Double = 0
  func precedes(other: Number) -> Bool {
        return value < other.value 
  }
}
最后,需要注意的是,一旦在protocol中使用了Self,那么该protocol就不能做类型限定符了,即不能再使用该类型指定参数,属性的类型,其只能用于泛型中。因此,需要把最先的二分查找函数修改如下:
func binarySearch<T:Ordered>(sortedKeys: [T], forKey k: T) -> Int {
    var lo = 0, hi = sortedKeys.count
    while hi > lo {
        let mid = lo + (hi - lo) / 2
        if sortedKeys[mid].precedes(k) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}

通过这个特性,可以让代码更加清晰,隔离性更好,处理Number时,只需要在意Number而无需去考虑其它的实现了Ordered的对象。同时,因为整个程序运行时的类型都是固定的,对编译器优化程序也有很大的帮助。

Protocol Extension,

第二个大鸡蛋:学名叫做Protocol Extension。 在使用接口时,经常会出现如下的场景,几个类实现了同一个接口,接口的其中一个方法,在几个类中实现都是一样的。这个时候就会出现几个类有重复代码的情形,一般有2种方式来消除掉这种重复,一是给这几个毫无关系的类加一个共同父类来做代码共享(强烈建议不要这样做);二是把这个方法抽出到一个单独的类中,再通过组合方式把功能引回需要使用的类(比较麻烦)。而protocol extension就完美的解决了这样的问题。如下:

定义一个protocol:
import CoreGraphics
protocol Renderer {
  func circleAt(center: CGPoint, radius: CGFloat)
  func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
}

如果协议中的某个方法,比如说circleAt,所有实现类的实现都是一样的, 那么可以做一个protocol extension,添加该方法的默认实现:
extension Renderer {
  func circleAt(center: CGPoint, radius: CGFloat) {
    print("protocol circleAt")
  }
}

另外,如果几个类中,大部分都通用,就只有1,2个类实现方式不一样,protocol extension同样可以处理,只需要在类中实现自己想要的行为就行了:
struct Circle : Renderer {
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        print("Circle arcAt")
    }

    func circleAt(center: CGPoint, radius: CGFloat) {
        print("Circle circleAt")
    }
}

struct Triangle : Renderer {
    func arcAt(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat) {
        print("Triangle arcAt")
    }
}

程序在执行时,会先从当前类中查找想要的方法,没找到才会继续向上查找protocol extension中的默认实现。如下:
func test(r:Renderer) {
        r.circleAt(CGPoint(x: 1, y: 1), radius: 1.2)
}

test(Circle()) //会打印Circle circleAt
test(Triangle()) //会打印protocol circleAt

除了上面提到的之外,Protocol Extension还提供了一个有趣的特性:

限制扩展支持的类型 :即你可以在实现Protocol Extension时,直接指定必须满足某种条件才能使用该扩展中的方法,前面提到的是子类决定要不要接受Protocol Extensio中的默认实现,而这个特性是Protocol Extension设置默认实现的使用门槛。 比如:只有集合中的对象支持Equatable才能使用默认indexOf方法:
extension CollectionType where Generator.Element : Equatable {
    public func indexOf(element: Generator.Element) -> Index? {
        for i in self.indices {
            if self[i] == element {
                return i
            }
        }
        return nil
    }
}

有了这个特性,就可以重新组合方法定义的位置,并把泛型抽取到定义扩展时,从而使得方法的定义更加清晰,也更加美观,如下:

美颜前
func binarySearch<C : CollectionType where C.Index == RandomAccessIndexType, C.Generator.Element : Ordered >(sortedKeys: C, forKey k: C.Generator.Element) -> Int {
  ...
}

let pos = binarySearch([2, 3, 5, 7, 11, 13, 17], forKey: 5)

美颜后
extension CollectionType where Index == RandomAccessIndexType, Generator.Element : Ordered {
  func binarySearch(forKey: Generator.Element) -> Int {
    ...
  } 
}

let pos = [2, 3, 5, 7, 11, 13, 17].binarySearch(5)


总结

从上面的讲述,可以看得出来,Swift2.0带来的Protocol Extension算是针对使用面向接口编程遇到的各种问题提供了一个比较用心的解决方案, 因此,在Swift使用Protocol Oriented Pragraming是一个非常不错的选择,整个思维模式和以前的面向接口编程并无多大的差别,同时还可以享受到苹果提供的这些贴心小特性, 你已经比大Java程序员幸福多了。
2
2
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics