Protocols
顾名思义它是一种约定,在Swift
中protocol
比较类似于其他面向对象语言中的interface(接口)
。空口说还是比较抽象,我们还是先看代码。
protocol Staff{
func work()
}
class Programmer:Staff{
func work() {
print("我在写代码")
}
}
class Designer:Staff{
func work() {
print("我在搞设计")
}
}
let 张三 = Programmer()
let 李四 = Designer()
张三.work()
李四.work()
除了开头三行定义protocol
的代码,其他的我们之前都用过或者见过类似的。即使不运行代码,你应该也能猜到最后程序的输出内容。可能引起费解的应该是定义Programmer
的Programmer:Staff
以及Designer:Staff
这段,因为我们之前介绍过,如果需要一个类继承另外一个类,就是需要用:
做连接的。但是此刻,显然我们不是在做继承,因为前面那个叫Staff
就不是class
,当然也不能当作父类
。Staff
前面的关键字亮明了身份,它就是我们今天要介绍的protocol
。
protocol
可以用来规定一组行为,它自己并不告诉我们这些行为具体该怎么做,但是它可以用来约定其他人,要求这些其他人必须把这些事情做到位,这就是protocol
的主要用途。
结合上面的例子已经很好理解了。工作需要有职工 Staff
,他们的主要能力就是工作 work
,但是不同的工种有不同的work
内容。所以Programmer
、Designer
都有不同的work
函数的具体实现(例子中就是打印一句话)。因为Programmer
、Designer
都被Staff
约束着,你就可以想象,存在一位老板,他会口头禅就是:“我管你是什么工种,来我这就得给我干活”。
此时,如果我们再创造一个类ProductManager
,它也通过:Staff
受到约束,但是它就不干活,我们来看看会怎样。
如上图,我们没有在ProductManager
里提供work
函数,此时编译器就会报错了,告诉我们这个ProductManager
违背了Staff
的约定。
protocol
不光可以约定别人的函数,也可以约定别人的属性。接着我们的例子说,老板对每位员工都一视同仁,管你是中国人还是外国人,都必须有个工号(jobNumber
),这就可以用protocol
来实现。
但是如果我们贸然对Staff
增加一个变量是不行的,就会收到编译器给出的报错,如上图。同时,编译器也给出了一个提示帮我们改进代码,改进过后的代码如下:
protocol Staff{
var jobNumber:String{get set}
func work()
}
当你改完Staff
之后,之前的Programmer
、Designer
也会同时跟着报错,因为编译器检查到它俩已经不在满足之前的约定了,也得与时俱进才行。这个时候,你只要在Programmer
、Designer
中追加属性var jobNumber: String
就可以了。但是因为它俩毕竟是class
,但是你声明了一个不为空的变量,又没有构造函数来初始化数据,肯定是说不过去的,编译器还是不愿意放过你,所以你会看到下面的报错:
如果你没有忘记之前我们讲过的init
方法的话,想修复这个问题其实很容易,最终Programmer
代码如下:
class Programmer:Staff{
var jobNumber: String
init(jobNumber:String){
self.jobNumber = jobNumber
}
func work() {
print("我在写代码")
}
}
那么创建一个程序员的代码也得稍加变动,还以张三为例的话,就得这样写了:
let 张三 = Programmer(jobNumber: "001")
这里留个小小的思考题给你,如果我想在张三.work()
时输出我是工号:001,我在写代码
,该怎么修改代码呢?
在前面的例子中,我们展示了protocol
是如何作用于class
的,但其实它也可以作用于struct
和enum
,主要用法差不多,但是有一点是值得注意的,因为struct
、enum
毕竟是值类型,默认不具备自己修改自己的能力,在一些特殊场景下还是要注意的。
下面我们就来看一下稍微特殊点的场景,回到前面的Staff
的例子,我们再给它加一个属性薪资 salary
,以及追加一个函数涨薪 salaryIncrease
,调整过的Staff
如下:
protocol Staff{
var salary:Float{get}
var jobNumber:String{get set}
func work()
func salaryIncrease(ratio:Float)
}
有一点注意的是,我们并没给salary``set
的机会,因为薪资嘛,岂能让别人说改就改,只有老板通过salaryIncrease
才能对员工变动薪资。同时,salaryIncrease
的参数我们设计为一个比例值,如果该值为0.1
的话,就是涨薪10%
的意思。下面我们来优化Programmer
,最终代码如下:
class Programmer:Staff{
var salary: Float
var jobNumber: String
init(jobNumber:String, salary:Float){
self.jobNumber = jobNumber
self.salary = salary
}
func work() {
print("我是工号:\(jobNumber),我在写代码")
}
func salaryIncrease(ratio: Float) {
self.salary = self.salary + self.salary*ratio
}
}
上面的代码是基于class
的,当我们把关键字class
改成struct
的时候,它就变成结构体了。但是同时,我们会收到一个报错:
也就是前面提到的struct
默认不能自己改自己的问题。此时你需要声明这个方法是mutating
的,从而获得可修改自己的能力。
mutating func salaryIncrease(ratio: Float) {
self.salary = self.salary + self.salary*ratio
}
当然这样改了之后,protocol
那边也得要改,最终Staff
代码如下:
protocol Staff{
var salary:Float{get}
var jobNumber:String{get set}
func work()
mutating func salaryIncrease(ratio:Float)
}
这样改动之后,如果我们真的想给张三加薪的话,就可以通过下面的代码实现:
var 张三 = Programmer(jobNumber: "001",salary: 1000)
print(张三.salary) //1000.0
张三.salaryIncrease(ratio: 0.1)
print(张三.salary) //1100.0
顺便说一下,因为class
也可以实现protocol
,但是约定方法的关键字mutating
是不适用class
的,那么这样含有mutating
的protocol
还能用来约定class
么?
答案是可以的,但是要注意的是,虽然protocol
中的函数是有mutating
作为修饰,但是在class
中,要把mutating
关键字删除就行了。说起来有点绕,这里我们分别用Programmer
作为class
、Designer
作为struct
来给大家演示一下,最终代码如下:
protocol Staff{
var salary:Float{get}
var jobNumber:String{get set}
func work()
mutating func salaryIncrease(ratio:Float)
}
class Programmer:Staff{
var salary: Float
var jobNumber: String
init(jobNumber:String, salary:Float){
self.jobNumber = jobNumber
self.salary = salary
}
func work() {
print("我是工号:\(jobNumber),我在写代码,我的薪资是\(salary)")
}
func salaryIncrease(ratio: Float) {
self.salary = self.salary + self.salary*ratio
}
}
struct Designer:Staff{
var salary: Float
var jobNumber: String
init(jobNumber:String, salary:Float){
self.jobNumber = jobNumber
self.salary = salary
}
func work() {
print("我是工号:\(jobNumber),我在搞设计,我的薪资是\(salary)")
}
mutating func salaryIncrease(ratio: Float) {
self.salary = self.salary + self.salary*ratio
}
}
let 张三 = Programmer(jobNumber: "001",salary: 1000)
张三.salaryIncrease(ratio: 0.1)
张三.work()
var 李四 = Designer(jobNumber: "002",salary: 1000)
李四.salaryIncrease(ratio: 0.15)
李四.work()
得到的输出结果会是:
我是工号:001,我在写代码,我的薪资是1100.0
我是工号:002,我在搞设计,我的薪资是1150.0
上面的代码中还有个小细节,张三
我们用let
声明,而李四
却用的var
,这可不是笔误,你能参透其中的原因么?
最后还要强调的是,用:
去约定protocol
是可以同时实现多个的,但是继承父类,只能继承一个,它们都是用唯一的:
来做连接的。如果存在既要继承class
又要实现protocol
的情况,应该把父类放在紧随:
之后,如图:
参考资料:
- https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
- https://docs.swift.org/swift-book/GuidedTour/GuidedTour.html