在Swift中,根据已有的、明确的规则或约定,描述和匹配一组值的方式,我们可将其称之为一种编码模式,比如:
- 所有的元组在取数据时是从0开始的。
- 表示数字的范围我们可以使用
1...5
这种形式。 - 匹配或判断某些类实例的类型。
该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
类型。
|
我们通过for
循环语句,让fizzBuzz
函数参数从1到100执行100次,模拟我们在游戏中从1数到100。然后看看控制台输出的结果。
|
在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!”。
|
如果number
等于6,那么元组的结果为(0, 1)
,这将符合switch
语句中的第二个case
判断(0, _)
,因为下划线在Swift中约定是通配符,它代表任何值。所以将返回“Fizz!”。
|
如果number
等于11,那么元组的结果为(2, 1)
,这将符合switch
语句中的第四个case
判断(_, _)
,因为第四个case
判断的是既不能被3整除又不能被5整除的情况,所以用两个下划线表示元组中的两个成员。返回结果为“11”。
练习:
让fizzBuzz
函数根据其他数字返回不同的消息。
再加一种数字的特殊情况,让该函数返回“Bang!”。使用返回“Fizz!”和“Buzz!”相同的模式,比如如果number
能被7整除,就返回“Bang!”。别忘了还有“FizzBuzzBang!”这种情况,尽可能将case
情况列举全。
如果最后一个case
你用default
代替case (_ ,_)
会发生什么呢?这两种方式都能正确的返回不满足其他case
的值么?
枚举和关联值
使用枚举和它的关联值匹配枚举中特定的case
场景也是一种匹配模式。下面的例子使用枚举展示了火车的到站时间状态。
|
如果火车正点到站,那么它的状态为Status.OnTime
,并且没有关联值。当火车晚点,那么它的状态为Status.Delayed(Int)
,并需要传入一个关联值用于表示火车到底晚了多久。
|
这里有一个名为Train
的类,包含一个status
属性,默认值为Status.OnTime
。
|
你可以使用匹配模式,将Status.Delayed(Int)
这种情况的关联值提出来进行判断。下面的代码将Train
类进行了扩展,使之遵循Printable
协议,添加了一个只读属性description
。这个扩展可以很容易的检索出包含火车晚点分钟数的String
字符串,并返回。
|
switch
语句中的第一个case
用于匹配当火车状态为OnTime
时的情况,并返回简单的字符串。
第二个case
要稍复杂一些,它创建了一个临时常量minutes
来表示传入的关联值,并用where
关键字申明一个0到5的范围,判断minutes
是否在该范围内,如果在该范围内,那么将这个关联值嵌入字符串返回。
第三个case
用于匹配不在晚点范围内的情况。
你现在可以创建一些Train
类的实例验证一下。
|
然后使用description
属性查看每个Train
实例的状态值。
|
练习:
改变trainTwo
和trainThree
的status
属性,看看它们的description
属性有何变化。
改变Train
扩展中switch
的最后一个case
语句,让它返回包含关联值的字符串,比如“Delayed by 17 min”。
加分项,再增加一种case
情况,当关联值大于60时,返回列车晚点几小时几分的字符串。
提示:可以使用>=
操作符。
检查和转换子类型
还有一种模式可以让你动态的匹配类的实例。考虑一下下面代码中类的所属关系:
|
有一种简单的类型匹配模式,使用is
关键字就可以进行父类与子类之间的匹配和判断。
|
你可以将刚才创建的Train
类和MaglevTrain
类实例传入trainDescription
函数,看看会有什么结果。
|
练习:
在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
的返回值。
|
练习:
在SteamTrain
类中添加一个名为cleanFirebox
的函数,在determineMaintenanceRequirements
函数中的switch
语句里添加一个case
语句,用于判断对象的类型是不是SteamTrain
的子类,如果是,将对象的类型转换为SteamTrain
类型,并调用SteamTrain
类的cleanFirebox
函数。然后将SteamTrain
类的实例传入determineMaintenanceRequirements
函数看看是否能返回正确的描述信息。
原文地址:Patterns Playground