微信号:swift_info

介绍:关注swift编程,分享最新互联网资讯、移动端建设、swift技术、PHP技术、搜索引擎SEO、数据库开发.更多精彩内容,请查看历史记录!

通过Playground展示一些编码模式

2014-10-13 10:08 悟度

在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类型。

func fizzBuzz(number: Int) -> String {     switch (number % 3, number % 5) {     case (0, 0):         // number既能被3整除又能被5整除         return "FizzBuzz!"     case (0, _):         // number能被3整除         return "Fizz!"     case (_, 0):         // number能被5整除         return "Buzz!"     case (_, _):         // number既不能被3整除也不能被5整除         return "\(number)"     } }

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

for i in 1...100 {     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!”。

fizzBuzz(15)

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

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场景也是一种匹配模式。下面的例子使用枚举展示了火车的到站时间状态。

enum Status {     case OnTime     case Delayed(minutes: Int) }

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

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

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

class Train {     var status = Status.OnTime }

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

extension Train: Printable {          var description: String {          switch status {          case .OnTime:             // 满足正点到达的情况,返回“On time”             return "On time"          case .Delayed(let minutes) where 0...5 ~= minutes:             // 将传入的关联值通过“~=”操作符在一定范围内匹配             return "Slight delay of \(minutes) min"          case .Delayed(_):             // 用下划线通配符匹配不在晚点分钟数范围内的情况             return "Delayed"          }      }      }

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

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

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

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

let trainOne = Train() let trainTwo = Train() let trainThree = Train()  trainTwo.status = .Delayed(minutes: 2) trainThree.status = .Delayed(minutes: 8)

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

trainOne.description trainTwo.description trainThree.description

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

检查和转换子类型

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

extension Train {     func cleanPassengerCars() -> String {         return "Clean the passenger cars"     } }  class MaglevTrain: Train {     func referToSpecialist() -> String {         return "Refer the maglev to a specialist"     } }  let maglev = MaglevTrain() let train = Train()

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

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

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

trainDescription(maglev) 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的返回值。

func determineMaintenanceRequirements(train: Train) -> String {     switch train {     case let maglev as MaglevTrain:         return maglev.referToSpecialist()     default:         return train.cleanPassengerCars()     } }  determineMaintenanceRequirements(train) determineMaintenanceRequirements(maglev)

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

 
swift编程 更多文章 关于 Swift 学习苹果Swift语言的一些在线资源 Swift语言将会对IT行业未来产生什么影响? 程序员眼中的苹果Swift语言:简单 易学 高效 谷歌发布全新设计语言:跟苹果Swift天壤之别
猜您喜欢 敏捷破冰之旅(三) Facebook图片存储架构的学习 Neutron社区每周记(5.2~5.8)|Newton版本网络三大发展规划 那些给“单身狗”带来一万点伤害的神秘天敌,简直不敢细看 【报名】中国最顶级产品经理&产品运营社群6月启动,中国互联网技术联盟发起