MENU

面向协议编程

Swift语言由于先天的后发优势吸收了很多好的编程概念和特性,这些特性在给开发者带来新可能的同时也进一步促进了语言本身的进化。这也是经历三次Swift从入门到重学也不离不弃的原因?。Swift语言的优势和特性大抵如下:

  • 跨平台。Linux平台的服务端正在发力,框架也在快速发展中。

  • 开源给这门语言带来了无限可能。

  • OOP、POP、函数式这些编程理念全支持

这篇文章将会对其中的POP概念进行简单讲解。POP概念的传播起点大概就是在“喜新厌旧”的苹果在2015年WWDC上鼓励使用Value Type来替换Reference Type。但是我们需要明白一点:开发世界没有银弹。POP作为一个理念和特性有其适用场景并不能完全替代OOP,后者存在几十年是有理由的。

值类型与引用类型

上面说了POP兴起于Value Type被鼓励推崇后,那么我们就有必要理解两者的区别。值类型和引用类型最根本的区别就在于复制后的两者的表现。做个类比:值类型复制动作相当于克隆了自己,你和克隆的对象之间是相互独立的;引用类型复制动作后两者的关系相当于自己和影子。类比不是很贴切,但是不妨碍理解。下面用代码来说明:

Class HumanClass {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

var classyHuman = HumanCalss(name: "Bob")
classyHuman.name //Bob

var newClassyHuman = classyHuman    //复制一份

newClassyHuman.name = "Bobby"
classyHuman.name        //"Bobby"

当我们改变newClassyHuman对象的name属性后,类对象classyHuman也跟着发生了变化。

接下来看值类型:

struct HumanStruct {
    var name: String 
}

var humanStruct = HumanStruct(name: "Bob")
var newHumanStruct = humanStruct        //同样复制

newHumanStruct.name = "Bobby"
humanStruct.name                                //"Bob"

拷贝对象的属性改变并没有影响到原对象。

OOP的缺陷

作为几十年前的一种理念,OOP可以说已经在现代被用烂了,几乎所有的编程语言都支持这一特性。Java、Ruby等语言的设计理念中几乎将一切事物都看作对象,对象即中心、对象即真理。但是我们反过头来看OOP,其实可以很清晰的发现一些固有的缺陷。

  1. 继承机制要求你在开始之前就能设计好整个程序的框架、结构、事物间的连接关系。这要求开发者必须有很好的分类设计能力,因为结构天生对改动有抵抗特性。这也是为什么OOP领域中所有程序员都对重构讳莫如深,有些框架到最后代码量急剧膨胀变得难以维护从而失控。

  2. 继承机制带来的另一个问题就:子类中会存在无用的父类属性和方法,而这些冗余代码给子类带来的一定的风险,而且对于层级很深的代码结构来说Bug修复将会成为难题。

  3. 对象的状态不是我们的编码的好友,相反是我们的敌人。对象固有的状态在分享和传递过程中是很难追踪调试的,尤其在并行程序编码中问题就更加明显。OOP所带来的可变、不确定、复杂等特征完全与并行编程中倡导的小型化、核心化、高效化完全背离。而免费午餐时代已经结束、摩尔定律的吃力表现意味着接下来的世界是属于多核、并行、并发编程的。

这里我们顺便看下Apple用OOP思想完成的UIKit框架:

UIKit Framework Structure

整体结构设计其实非常的清晰,但是如果让你在Apple对这个框架进行维护你有多少的自信能够出色完成任务?别说框架维护了,其实我们大部分开发者都仅仅停留在框架的语言应用层面并没有很大的耐心去厘清这个结构清晰的OOP框架。

欢迎来到POP的世界

你应该能够猜到了与OOP以引用类型Class为基础不同,POP理念的核心是值类型Struct。OOP是一种类金字塔的架构来搭建世界,POP则是一个扁平、非嵌套的代码世界。

"A protocol defines a blueprint of methods, properties… The protocol can then be adopted by a class, structure, or enumeration" - Apple

上面是Apple对协议的定义说明,其中最关键的字眼莫过于“blueprint"。其实Protocal类似于篮球教练,他告诉球员们如何执行战术、如何赢得比赛,但是他本人可能并不知道如何完成一个漂亮的大风车扣篮。

初识POP

首先,我们为人类建立一个特征蓝图。


protocol Human {
    var name: String {get set }
    var  carrer: String {get set }
    func sayHi() 
}

上面的协议中,我们除了声明一些人类共有的特质没有做任何其他的事情。其中的{get set }仅仅只是表面这些属性是可读写的,下面我们让运动员遵循该协议:

struct Athlete: Human {
    var name: String = "Kobe Bryant"
    var carrer:   String - "Basketball Player"
    
    func satHi() { print("Hi,I'm \(name)" ) }
}

一旦Struct遵循了Human协议,它就必须按照规则实现所有的协议属性和方法。当然Xcode同学会提醒你协议中的那些是你需要必须实现的,所以你不必害怕遗忘了其中的部分内容。

协议的继承

在Protocol的世界中,我们可以使用OOP中的继承概念对协议进行继承形成协议树。需要注意的是这同样会带来OOP中的恶果,不宜滥用。这里我们仅仅是为了更加全面的介绍POP中协议所包含的各种能力。

我们在Human协议基础上实现SuperHuman的协议。

protocol SuperHuman: Human {
    var canFly: Bool { get set } }
    func punch()
}

如果现在有一个Struct遵循了SuperHuman协议,那么除了SuperHuman协议中的内容,我们同时也要实现Human协议中的内容。

struct SuperSaiyan: SuperHuman {
    var name: String = "Goku"
     var race: String = "Asian"
     var canFly: Bool = true
    func sayHi() { print("Hi, I'm \(name)") }
    func punch() { print("Puuooookkk") } 
}

当然我们也可是同时遵循多个协议,这在某种程度上来说相当于C++语言中类的多继承的变通版本。

struct SuperSaiyan: SuperHuman, ProtocolAnother { }

协议拓展

协议拓展(Protocol Extension)是协议使用过程中最强大的武器,下面我们直接上码:

// 会说英语的动物
protocol SuperAnimal {
    func speakEnglish() 
}

接下来我们对其进行拓展:

extension SuperAnimal {
    func speakEnglish() {
        print("I speak English, pretty coo;, huh?")
    }
}

最后我们看看效果如何:

struct Donkey: SuperAnimal{

}

var ramon = Donkey()
ramon.speakEnglish()        //  "I speak English, pretty cool, huh?"

从上面的演示效果我们可以发现:通过协议拓展我们给协议的遵循者一些默认的方法、属性的实现。

作为Type的Protocol

如果我告诉你可以在数组里面同时保存Struct和Class对象而不用进行任何形式的类型转换,你会作何感想?
是不是觉得不可思议,这确实有点突破常规,但同时在Swift中也是真实存在的。

直接上码:

protocol Fightable {
    func legKick() 
}

struct StructKangaroo: Fightable {
     func legKick() { print("Puuook") }
 }
 
 class ClassKangaroo: Fightable { 
     func legKick() {print("Pakkkk") } 
}

let structKang = StructKangaroo()
let classKang = ClassKangaroo()

var kangaroos: [Fightable] = [structKang, classKang]

for kang in kangaroos { 
    kang.legKick() 
}
// "Puuook"
// "Pakkkk"

我们将同时采用Fightable协议的Class、Struct对象保存到了[Fightable]类型的数组中,并且可以按照正常的数组一样进行操作,是不是很神奇。

结语

编程世界没有银弹,每一种理念都有其存在的价值。这篇文章中我简单的介绍了POP的概念以及Protocol世界中的一些语法糖,真正的POP需要你自己在这些基础知识和语法糖的上层去应用。

标签: Swift, POP, Protocol