首页 >Swift

通过Playground展示一些编码模式

2014-09-09 13:57 编辑: wanglei 分类:Swift 来源:CocoaChina

本节由CocoaChina翻译组成员DevTalking (博客 )翻译自苹果官方文档Patterns Playground一节,敬请勘误。欢迎加入我们的翻译小组,详情请参看:CocoaChina编辑和译者招募!   

 

在Swift中,根据已有的、明确的规则或约定,描述和匹配一组值的方式,我们可将其称之为一种编码模式,比如:

    • 1.所有的元组在取数据时是从0开始的。
    • 2.表示数字的范围我们可以使用1...5这种形式。
    • 3.匹配或判断某些类实例的类型。

该Playground文件需要使用OS X Mavericks或OS X Yosemite beta系统中的Xcode6打开。

 

Patterns.playground

这个playground文件介绍了一些匹配模式的概念。在Swift中,你可以使用条件语句(比如switch语句)通过简明、易读的方式匹配多个值,这种方式就是一种模式。

注意:
如果你看不到控制台输出界面,你可以通过View > Assistant Editor > Show Assistant Editor选项或使用Option-Command-Return快捷键打开Timeline区域。

 

匹配元组中的值

下面这个例子向你展示了如何使用匹配模式写出简明、优雅的switch语句。在这个例子中使用了FizzBuzz游戏作为场景进行说明。我们先来简单介绍一下这个游戏,在FizzBuzz游戏中,你从1开始数数,如果你数到的数字能被3整除,那么你就不能说出该数字而要说“Fizz”。如果你数到的数字能被5整除,那么你就要说“Buzz”。如果你数到的数字既能被3整除又能被5整除,那么你就要说“FizzBuzz”。所以一般情况下数数的情形像这样:“1,2,Fizz,4,Buzz...”。那么在这个例子中我们用一个名为fizzBuzz的函数代表该游戏,这个函数有一个参数,代表我们要数的数字,因为我们需要说出“Fizz”、“Buzz”以及“FizzBuzz”,所以返回值为String类型。

  1. func fizzBuzz(number: Int) -> String { 
  2.     switch (number % 3, number % 5) { 
  3.     case (0, 0): 
  4.         // number既能被3整除又能被5整除 
  5.         return "FizzBuzz!" 
  6.     case (0, _): 
  7.         // number能被3整除 
  8.         return "Fizz!" 
  9.     case (_, 0): 
  10.         // number能被5整除 
  11.         return "Buzz!" 
  12.     case (_, _): 
  13.         // number既不能被3整除也不能被5整除 
  14.         return "\(number)" 
  15.     } 

我们通过for循环语句,让fizzBuzz函数参数从1到100执行100次,模拟我们在游戏中从1数到100。然后看看控制台输出的结果。

  1. for i in 1...100 { 
  2.     println(fizzBuzz(i)) 

在fizzBuzz函数中的switch语句中,判断表达式是一个元组,它包含两个成员,这两个成员也是表达式。第一个表达式number % 3,意思是number取3的余数,第二个表达式number % 5,意思是number取5的余数。每一个case语句都对该元组中这两个表达式计算出的值进行匹配判断。

 

比如,如果number等于15,那么(number % 3, number % 5)的结果就是(0, 0),这代表15既能被3整除又能5整除。这符合switch语句中的第一个case判断,所以返回“FizzBuzz!”。

  1. fizzBuzz(15) 

 如果number等于6,那么元组的结果为(0, 1),这将符合switch语句中的第二个case判断(0, _),因为下划线在Swift中约定是通配符,它代表任何值。所以将返回“Fizz!”。

  1. fizzBuzz(6) 

如果number等于11,那么元组的结果为(2, 1),这将符合switch语句中的第四个case判断(_, _),因为第四个case判断的是既不能被3整除又不能被5整除的情况,所以用两个下划线表示元组中的两个成员。返回结果为“11”。

 

练习:
让fizzBuzz函数根据其他数字返回不同的消息。
再加一种数字的特殊情况,让该函数返回“Bang!”。使用返回“Fizz!”和“Buzz!”相同的模式,比如如果number能被7整除,就返回“Bang!”。别忘了还有“FizzBuzzBang!”这种情况,尽可能将case情况列举全。
如果最后一个case你用default代替case (_ ,_)会发生什么呢?这两种方式都能正确的返回不满足其他case的值么?

 

枚举和关联值

使用枚举和它的关联值匹配枚举中特定的case场景也是一种匹配模式。下面的例子使用枚举展示了火车的到站时间状态。

  1. enum Status { 
  2.     case OnTime 
  3.     case Delayed(minutes: Int) 

如果火车正点到站,那么它的状态为Status.OnTime,并且没有关联值。当火车晚点,那么它的状态为Status.Delayed(Int),并需要传入一个关联值用于表示火车到底晚了多久。

  1. let goodNews = Status.OnTime 
  2. let badNews = Status.Delayed(minutes: 90) 

这里有一个名为Train的类,包含一个status属性,默认值为Status.OnTime。

  1. class Train { 
  2.     var status = Status.OnTime 

你可以使用匹配模式,将Status.Delayed(Int)这种情况的关联值提出来进行判断。下面的代码将Train类进行了扩展,使之遵循Printable协议,添加了一个只读属性description。这个扩展可以很容易的检索出包含火车晚点分钟数的String字符串,并返回。

  1. extension Train: Printable { 
  2.      
  3.     var description: String { 
  4.  
  5.         switch status { 
  6.  
  7.         case .OnTime: 
  8.             // 满足正点到达的情况,返回“On time” 
  9.             return "On time" 
  10.  
  11.         case .Delayed(let minutes) where 0...5 ~= minutes: 
  12.             // 将传入的关联值通过“~=”操作符在一定范围内匹配 
  13.             return "Slight delay of \(minutes) min" 
  14.  
  15.         case .Delayed(_): 
  16.             // 用下划线通配符匹配不在晚点分钟数范围内的情况 
  17.             return "Delayed" 
  18.  
  19.         } 
  20.  
  21.     } 
  22.      

switch语句中的第一个case用于匹配当火车状态为OnTime时的情况,并返回简单的字符串。

 

第二个case要稍复杂一些,它创建了一个临时常量minutes来表示传入的关联值,并用where关键字申明一个0到5的范围,判断minutes是否在该范围内,如果在该范围内,那么将这个关联值嵌入字符串返回。

 

第三个case用于匹配不在晚点范围内的情况。

 

你现在可以创建一些Train类的实例验证一下。

  1. let trainOne = Train() 
  2. let trainTwo = Train() 
  3. let trainThree = Train() 
  4.  
  5. trainTwo.status = .Delayed(minutes: 2) 
  6. trainThree.status = .Delayed(minutes: 8) 

然后使用description属性查看每个Train实例的状态值。

  1. trainOne.description 
  2. trainTwo.description 
  3. trainThree.description 

 

练习:
改变trainTwo和trainThree的status属性,看看它们的description属性有何变化。
改变Train扩展中switch的最后一个case语句,让它返回包含关联值的字符串,比如“Delayed by 17 min”。
加分项,再增加一种case情况,当关联值大于60时,返回列车晚点几小时几分的字符串。
提示:可以使用>=操作符。

 

检查和转换子类型

还有一种模式可以让你动态的匹配类的实例。考虑一下下面代码中类的所属关系:

  1. extension Train { 
  2.     func cleanPassengerCars() -> String { 
  3.         return "Clean the passenger cars" 
  4.     } 
  5.  
  6. class MaglevTrain: Train { 
  7.     func referToSpecialist() -> String { 
  8.         return "Refer the maglev to a specialist" 
  9.     } 
  10.  
  11. let maglev = MaglevTrain() 
  12. let train = Train() 

有一种简单的类型匹配模式,使用is关键字就可以进行父类与子类之间的匹配和判断。

  1. func trainDescription(train: Train) -> String { 
  2.     switch train { 
  3.     case is MaglevTrain: 
  4.         return "The fastest train on earth." 
  5.     default
  6.         return "Some other kind of train." 
  7.     } 

你可以将刚才创建的Train类和MaglevTrain类实例传入trainDescription函数,看看会有什么结果。

  1. trainDescription(maglev) 
  2. trainDescription(train) 

 

练习:
在trainDescription函数中的switch语句中再添加一个case,用于匹配判断train是不是Train类型的,然后看看会提示什么错误?为什么呢?
再定义一个Train类的子类SteamTrain,然后在trainDescription函数的switch语句中添加一个case,用于匹配判断train的类型是不是SteamTrain,然后返回适当的字符串描述。将SteamTrain实例传入trainDescription函数,看看是否返回正确的描述。

 

不过使用is关键字匹配类型只适用于检查子类。如果你想检查对象类型是不是某个类的子类,并且想使用父类的属性或方法时,可以使用as关键字(作用类似类型强制转换中的as)将判断的对象类型转换为父类型,这样在switch语句中就可以同时进行类型检查和类型转换了。使用as关键字时,需要先创建一个临时常量,用于表示需要判断或转换的对象。

 

下面的代码中有一个名为determineMaintenanceRequirements的函数,在switch语句中判断对象的类型是不是MaglevTrain的子类,如果是MaglevTrain的子类,那么将该对象的类型转换为MaglevTrain。如果转换成功,就可以使用转换后类型的方法。如果失败则返回default的返回值。

  1. func determineMaintenanceRequirements(train: Train) -> String { 
  2.     switch train { 
  3.     case let maglev as MaglevTrain: 
  4.         return maglev.referToSpecialist() 
  5.     default
  6.         return train.cleanPassengerCars() 
  7.     } 
  8.  
  9. determineMaintenanceRequirements(train) 
  10. determineMaintenanceRequirements(maglev) 

 

练习:
在SteamTrain类中添加一个名为cleanFirebox的函数,在determineMaintenanceRequirements函数中的switch语句里添加一个case语句,用于判断对象的类型是不是SteamTrain的子类,如果是,将对象的类型转换为SteamTrain类型,并调用SteamTrain类的cleanFirebox函数。然后将SteamTrain类的实例传入determineMaintenanceRequirements函数看看是否能返回正确的描述信息。

搜索CocoaChina微信公众号:CocoaChina
微信扫一扫
订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina
我要投稿   收藏文章
上一篇:学习Swift中的CoreImage(图形核心编程)
下一篇:Swift更新至1.0版本
我来说两句
发表评论
您还没有登录!请登录注册
所有评论(0

综合评论

相关帖子

sina weixin mail 回到顶部