由 Dave Abrahams Professor 演講,這貨是C++牛人律罢,自行wiki。
筆記先從 Object Oriented Programming - OOP(面向?qū)ο缶幊蹋┱f(shuō)起棍丐,自然少不了 Class 這個(gè)主角误辑,當(dāng)初我入坑 iOS 時(shí)就問(wèn)過(guò)一個(gè)問(wèn)題:Class 和 Struct 的區(qū)別以及應(yīng)用場(chǎng)景?
當(dāng)然本文中不會(huì)詳盡地去比較孰優(yōu)孰劣歌逢,只是簡(jiǎn)單闡述下巾钉,僅供參考。
Classes Are Awesome
- Encapsulation(封裝)
- Access Control(訪問(wèn)控制)
- Abstraction(抽象)
- Namespace(命名空間)
- Expressive Syntax(表達(dá)語(yǔ)法)
- Extensibility(拓展性)
其中“Access Control”秘案、“Abstraction”和“Namespace” 指明是 Class 令人頭疼之處砰苍。
先說(shuō)說(shuō)繼承性,Class 完勝 Structure阱高,記住Structure是不具有繼承性的赚导!
類(lèi)可以將對(duì)象抽象成多個(gè)描述屬性和方法,可以通過(guò)繼承得到子類(lèi)赤惊,也就是傳統(tǒng)意義上的superclass 和 subclass吼旧。 子類(lèi)可以從父類(lèi)繼承一系列方法,當(dāng)然也可以通過(guò) override 重寫(xiě)父類(lèi)的方法未舟。
The Three Beef
- Implicit Sharing
- Inheritance All Up In Your Business
- Lost Type relationShips
第一點(diǎn):Class 實(shí)例需要在堆上分配內(nèi)存圈暗,此時(shí)有兩個(gè)對(duì)象A和B要操作該實(shí)例,那么傳遞給A和B類(lèi)實(shí)例的指針(內(nèi)存地址)即可裕膀,只有一個(gè)線(xiàn)程時(shí)不會(huì)有任何問(wèn)題员串,凡是有個(gè)先來(lái)后到,A操作完實(shí)例后B再接手操作對(duì)象昼扛;說(shuō)說(shuō)多線(xiàn)程情況寸齐,假設(shè)A在線(xiàn)程1對(duì)實(shí)例進(jìn)行讀操作,B在線(xiàn)程2對(duì)實(shí)例進(jìn)行寫(xiě)操作,來(lái)個(gè)巧合吧访忿!假設(shè)A在讀取實(shí)例中的數(shù)據(jù)量較大的數(shù)組(指向讀取舊數(shù)據(jù)),而B(niǎo)此刻卻在修改old數(shù)據(jù)斯稳,用new數(shù)據(jù)覆蓋海铆,美名其曰更新。此時(shí)意外就產(chǎn)生了挣惰!A寶寶心里苦啊卧斟,我只是想讀取舊數(shù)據(jù),你卻馬不停蹄地覆蓋舊數(shù)據(jù)憎茂。所以嘍珍语,以上情況想要解決就是復(fù)制一份實(shí)例! 但是過(guò)多的copy是否會(huì)讓你在操作時(shí)提心吊膽竖幔?
第二點(diǎn):Class 只能有一個(gè)父類(lèi)板乙,意味著只能從一個(gè)父類(lèi)繼承,而不能繼承多個(gè)類(lèi)拳氢。打個(gè)比方吧募逞,現(xiàn)在具有類(lèi)A和類(lèi)B,C具有A的所有特性馋评,所以嘍Class C:A{}
放接,C是A的子類(lèi);巧了留特!C具有B的所有特性纠脾,所以嘍Class C:B{}
; 但是卻沒(méi)有類(lèi)似Class C:A,B{}
這種多繼承寫(xiě)法蜕青。
關(guān)于第三點(diǎn)苟蹈,視頻舉了個(gè)binary Search(二分法查找) 例子:
// 對(duì)于有序隊(duì)列的一個(gè)抽象類(lèi) 關(guān)于隊(duì)列元素的類(lèi)型可以是Int String Double 等等
// 所以我說(shuō)這是一個(gè)抽象類(lèi)!
class Ordered {
func precedes(other: Ordered) -> Bool{
fatalError("implement me!")
}
}
// 全局函數(shù) 傳入一個(gè)排序好的數(shù)組 以及要查找的key值 通過(guò)二分法搜索返回索引值
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0, hi = sortedKeys.count
while hi > lo {
let mid = lo + (hi - lo) / 2
if sortedKeys[mid].precedes(k) { lo = mid + 1 }
else { hi = mid }
}
return lo
}
Question: 為什么不實(shí)現(xiàn)Ordered
類(lèi)中precedes
方法右核?
首先已經(jīng)強(qiáng)調(diào)了Ordered
是抽象類(lèi)汉操,當(dāng)然這并不是站得住腳的理由,看看下面兩個(gè)繼承自它的子類(lèi)蒙兰。
// 這是一個(gè)標(biāo)簽 所以包含String類(lèi)型的text屬性
class Label:Ordered{
var text:String = ""
...
}
// 這是一個(gè)數(shù)字 所以包含Double類(lèi)型的value屬性
class Number : Ordered {
var value: Double = 0
override func precedes(other: Ordered) -> Bool {
return value < other.value
}
}
// 還有其他類(lèi)型的Ordered 子類(lèi)
Label 嚴(yán)格意義上來(lái)說(shuō)是一個(gè)Ordered類(lèi)磷瘤,同理 Number 也是,因?yàn)樗麄兝^承自 Ordered搜变,具有其所有的特性采缚;剛才說(shuō)到你要為Ordered類(lèi)實(shí)現(xiàn)precedes
方法?請(qǐng)你告訴我對(duì)于傳入的 Ordered 類(lèi)到底是 Label 呢挠他,還是 Number 呢扳抽,亦或是其他呢? 要知道你根本無(wú)法確定!所以我們?cè)贠rdered類(lèi)中是不能實(shí)現(xiàn)的贸呢,而是讓子類(lèi)去重寫(xiě)實(shí)現(xiàn)镰烧。理解了這點(diǎn)繼續(xù)下面的內(nèi)容。
你會(huì)注意上面的 Number 類(lèi)中的 precedes 方法有點(diǎn)問(wèn)題:
class Number : Ordered {
var value: Double = 0
override func precedes(other: Ordered) -> Bool {
// 報(bào)錯(cuò)@阆荨U睢! 很好理解固蛾,傳入的 other 為 Ordered 類(lèi)即可
// 但是 Ordered 可沒(méi)有 value屬性结执! 所以我們需要進(jìn)行向下(父類(lèi)->子類(lèi))cast
return value < other.value
}
}
修改如下:
class Number : Ordered {
var value: Double = 0
// 實(shí)現(xiàn)也有缺陷 見(jiàn)下
override func precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
這里又有一個(gè)問(wèn)題,代碼要求傳入 Ordered 類(lèi)進(jìn)行處理艾凯,那么Number 和Label 嚴(yán)格意義上來(lái)說(shuō)都是 Ordered 類(lèi)(這里要理解因?yàn)樗鼈兌际荗rdered的子類(lèi))献幔,所以往代碼中傳入 Number 是Ok的, 而傳入Label類(lèi)時(shí)就很有問(wèn)題了趾诗, 因?yàn)橄蛳耤ast會(huì)出問(wèn)題(人家明明是Label 蜡感,你想要 as! 到 Number 類(lèi),絕壁失敗 程序crash)恃泪。所以嘍 問(wèn)題多多铸敏!
所以Abrahams提出了幾個(gè)不錯(cuò)的抽象機(jī)制:
- Supports value types (and classes)
- Supports static type relationships (and dynamic dispatch) Non-monolithic
- Supports retroactive modeling
- Doesn’t impose instance data on models
- Doesn’t impose initialization burdens on models
- Makes clear what to implement
面向協(xié)議編程
英文 Protocol-Oriented Programming ,面向協(xié)議編程自然少不了協(xié)議悟泵。簡(jiǎn)單來(lái)說(shuō)杈笔,首先將對(duì)象屬性(property)和行為(behavior)抽象成實(shí)例屬性和方法;將這些準(zhǔn)則整合成協(xié)議糕非;最后讓對(duì)象遵循這個(gè)協(xié)議并實(shí)現(xiàn)協(xié)議中的內(nèi)容即可蒙具。
改寫(xiě)上面例子:
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
現(xiàn)在 Ordered 是一個(gè)協(xié)議,它的準(zhǔn)則只有一個(gè) precedes
朽肥,因?yàn)槭菂f(xié)議禁筏,所以不需要具體實(shí)現(xiàn)。
現(xiàn)在我們說(shuō) Number 類(lèi)是有序的衡招,所以只需要遵循這個(gè)協(xié)議并實(shí)現(xiàn)要求的內(nèi)容即算滿(mǎn)足篱昔。
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
// 既然是POP 而非OOP,摒棄類(lèi)吧始腾,改成Struct 值類(lèi)型哦州刽!
struct Number : Ordered {
var value: Double = 0
// 去掉override 因?yàn)槲覀儾辉偈侵貙?xiě)父類(lèi)的方法 而是遵循協(xié)議
func precedes(other: Ordered) -> Bool {
return value < (other as! Number).value
}
}
還沒(méi)完,對(duì)于傳入的other類(lèi)型為 Ordered 類(lèi)型浪箭,早前因?yàn)槲覀兪侵貙?xiě)父類(lèi)方法穗椅,所以類(lèi)型上要保持一致,但是現(xiàn)在是POP奶栖,所以我們現(xiàn)在可以任性了匹表!改成Number门坷,去掉as! Number 這難看的轉(zhuǎn)換。
修改如下:
protocol Ordered {
func precedes(other: Ordered) -> Bool
}
// 既然是POP 而非OOP袍镀,摒棄類(lèi)吧默蚌,改成Struct 值類(lèi)型哦!
struct Number : Ordered {
var value: Double = 0
// 去掉override 因?yàn)槲覀儾辉偈侵貙?xiě)父類(lèi)的方法 而是遵循協(xié)議
func precedes(other: Number) -> Bool {
return value < other.value
}
}
不幸地是苇羡,編譯器報(bào)錯(cuò)“protocol requires function 'precedes' with type'(Ordered)->Bool' candidate has non-matching type '(Number)->Bool'”绸吸,不難理解,協(xié)議方法要求傳入 Ordered 類(lèi)型宣虾,而我們?cè)趯?shí)現(xiàn)時(shí)卻傳入 Number 類(lèi)型,嚴(yán)格來(lái)說(shuō)我們并未遵循Ordered協(xié)議温数。
值得引起注意绣硝,以及值得思考和牢記的地方:將協(xié)議中的Ordered類(lèi)型改寫(xiě)成 Self,這叫 “Self” requirement撑刺, Self 代表任何遵循這個(gè)協(xié)議的類(lèi)自身鹉胖,譬如 Number遵循 Ordered協(xié)議,那么實(shí)現(xiàn)的方法中 Self 會(huì)替換成 Number够傍; Label 遵循Ordered協(xié)議甫菠,那么其實(shí)現(xiàn)的方法中 Self 會(huì)替換成 Label,以此類(lèi)推冕屯。
protocol Ordered {
func precedes(other: Self) -> Bool
}
struct Number : Ordered {
var value: Double = 0
func precedes(other: Number) -> Bool {
return self.value < other.value
}
}
再來(lái)看看二分法查找全局函數(shù)的實(shí)現(xiàn):
func binarySearch(sortedKeys: [Ordered], forKey k: Ordered) -> Int {
var lo = 0
var hi = sortedKeys.count
while hi > lo {
let mid = lo + (hi - lo) / 2
if sortedKeys[mid].precedes(k) { lo = mid + 1 }
else { hi = mid }
}
return lo
}
編譯器又報(bào)錯(cuò)拉寂诱! “protocol 'Ordered' can only be used as a generic constraint because it has Self or associated type requirements”。 因?yàn)槲覀兪褂?Self 替換了明確的類(lèi)安聘,所以我們只能通過(guò)添加泛型約束才能解決問(wèn)題痰洒。ps:出錯(cuò)原因并非是函數(shù)傳入?yún)?shù)類(lèi)型為Ordered協(xié)議!浴韭! 很好奇為什么使用Self 或 關(guān)聯(lián)類(lèi)型就只能使用泛型約束了....2016/07/09 update: 感謝way的回答丘喻,實(shí)際上這樣的,盡管sortedKeys
和 forKey k
類(lèi)型都是Ordered
念颈,但這并不能保證兩個(gè)變量類(lèi)型保持一致泉粉,可能前者是Number
,后者是 Label
呢榴芳;而我們期望 xx.preceseds(yy)
這種方式調(diào)用時(shí) xx 和 yy 的類(lèi)型是相同的嗡靡,這也是協(xié)議明確說(shuō)明的,說(shuō)直白一些我們希望 xx 和 yy 的類(lèi)型都是 T窟感,但是類(lèi)型 T 必須遵循(實(shí)現(xiàn)) Ordered 協(xié)議叽躯。這也是為什么要用泛型約束了。
修改如下:
func binarySearch<T : Ordered>(sortedKeys: [T], forKey k: T) -> Int {
var lo = 0
var hi = sortedKeys.count
while hi > lo {
let mid = lo + (hi - lo) / 2
if sortedKeys[mid].precedes(k) { lo = mid + 1 }
else { hi = mid }
}
return lo
}
基于Protocol Oriented Programming 的繪圖例子
畫(huà)圖自然逃不了移動(dòng)點(diǎn)肌括,描繪線(xiàn)和圓弧等基本操作点骑,所以聲明一個(gè)描繪器 Renderer 合情合理吧:
struct Renderer {
// 以下幾個(gè)基本繪圖操作
func moveTo(p: CGPoint) { print("moveTo(\(p.x), \(p.y))") }
func lineTo(p: CGPoint) { print("lineTo(\(p.x), \(p.y))") }
func arcAt(center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat) {
print("arcAt(\(center), radius: \(radius),"
+ " startAngle: \(startAngle), endAngle: \(endAngle))")
}
}
接下來(lái)酣难,思考我們要繪制的圖形可不是簡(jiǎn)單的一個(gè)點(diǎn)或一條直線(xiàn),有可能是多邊形黑滴,復(fù)合圖形等等憨募。但終究逃不出“畫(huà)”這個(gè)動(dòng)詞以及需要一個(gè)描繪器Renderer。所以嘍袁辈,指定一個(gè)協(xié)議吧菜谣,它要求傳入一個(gè)renderer進(jìn)行繪圖:
protocol Drawable {
func draw(renderer: Renderer)
}
接下來(lái)想想如何描繪一個(gè)多邊形?貌似只要告知它起始點(diǎn)位置晚缩,要連接的下一個(gè)點(diǎn)位置就可以了吧尾膊!所以聲明一個(gè)Polygon結(jié)構(gòu)體(值類(lèi)型)如下:
struct Polygon : Drawable {
func draw(renderer: Renderer) {
// 移動(dòng)到指定點(diǎn)
renderer.moveTo(corners.last!)
// 只要按順序連接點(diǎn)即可
for p in corners {
renderer.lineTo(p)
}
}
// 一系列有循序的鏈接點(diǎn)
var corners: [CGPoint] = []
}
繪制一個(gè)圓需要知道圓心位置和半徑長(zhǎng)度即可:
struct Circle : Drawable {
func draw(renderer: Renderer) {
renderer.arcAt(center, radius: radius,
startAngle: 0.0, endAngle: twoPi)
}
var center: CGPoint
var radius: CGFloat
}
不管是繪制 Polygon 還是 Circle ,僅僅只是一個(gè)圖形罷了≤癖耍現(xiàn)在我們要更進(jìn)一步冈敛,告訴我要繪制的所有圖形(多邊形,圓形鸣皂,橢圓等等)抓谴,然后我將所有圖形都繪制到一張圖表上:
// 依舊遵循 Drawable 協(xié)議
struct Diagram : Drawable {
func draw(renderer: Renderer) {
for f in elements {
f.draw(renderer)
}
}
// 傳入要描繪的圖形
var elements: [Drawable] = []
}
Test It!
是時(shí)候測(cè)試下了!
var circle = Circle(center:
CGPoint(x: 187.5, y: 333.5),
radius: 93.75)
var triangle = Polygon(corners: [
CGPoint(x: 187.5, y: 427.25),
CGPoint(x: 268.69, y: 286.625),
CGPoint(x: 106.31, y: 286.625)])
var diagram = Diagram(elements: [circle, triangle])
diagram.draw(Renderer())
/// 終端打印信息
/*
$ ./test
arcAt((187.5, 333.5),
radius: 93.75, startAngle: 0.0,
endAngle: 6.28318530717959)
moveTo(106.310118395209, 286.625)
lineTo(187.5, 427.25)
lineTo(268.689881604791, 286.625)
lineTo(106.310118395209, 286.625)
$
*/
面向?qū)ο蟾M(jìn)一步
前面的 Renderer 作為結(jié)構(gòu)體存在寞缝,負(fù)責(zé)移動(dòng)點(diǎn)癌压、描線(xiàn)、畫(huà)圓弧等等荆陆,這是一個(gè)實(shí)例嘍滩届,但注意到這個(gè)Renderer實(shí)例中的三個(gè)基本繪圖方法執(zhí)行結(jié)果僅僅是打印信息,而不是真實(shí)地在屏幕上進(jìn)行繪圖被啼!太桑心了丐吓,所以嘍我們現(xiàn)在要依靠 CGContext 來(lái)進(jìn)行真實(shí)操作,首先要做的抽象一個(gè)描繪器要做的工作——實(shí)際我們已經(jīng)做好了趟据,只需要將Renderer結(jié)構(gòu)體改成協(xié)議即可券犁。如下:
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat)
}
要求 CGContext 遵循Renderer協(xié)議并進(jìn)行實(shí)現(xiàn):
extension CGContext : Renderer {
// 移動(dòng)點(diǎn) self 即CGContext實(shí)例本身 Self 值類(lèi)型本身
func moveTo(p: CGPoint) {
CGContextMoveToPoint(self, position.x, position.y)
}
// 連線(xiàn)
func lineTo(p: CGPoint) {
CGContextAddLineToPoint(self, position.x, position.y)
}
// 畫(huà)圓弧
func arcAt(center: CGPoint, radius: CGFloat,
startAngle: CGFloat, endAngle: CGFloat) {
let arc = CGPathCreateMutable()
CGPathAddArc(arc, nil, c.x, c.y, radius, startAngle, endAngle, true)
CGContextAddPath(self, arc)
}
}
效果圖:
協(xié)議擴(kuò)展和泛型
聲明Bubble形狀(Bubble氣泡,即一個(gè)圓中包含另外一個(gè)小圓):
struct Bubble : Drawable {
func draw(r: Renderer) {
// 描畫(huà)一個(gè)圓
r.arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
// 描畫(huà)另外一個(gè)圓
r.arcAt(highlightCenter, radius: highlightRadius,
startAngle: 0, endAngle: twoPi)
}
}
回憶早前的Circle形狀:
struct Circle : Drawable {
func draw(r: Renderer) {
// 畫(huà)圓
r.arcAt(center, radius: radius, startAngle: 0.0, endAngle: twoPi)
}
}
畫(huà)圓汹碱,畫(huà)圓粘衬,畫(huà)圓!咳促! 看來(lái)我們的描繪器renderer是時(shí)候新增一個(gè)畫(huà)圓操作了稚新!
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
// 新增方法
func circleAt(center: CGPoint, radius: CGFloat)
func arcAt(
center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat)
}
// 隨之而來(lái)的改動(dòng)自然就是TestRenderer要實(shí)現(xiàn)這個(gè)方法嘍
extension TestRenderer {
func circleAt(center: CGPoint, radius: CGFloat) {
arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
}
}
// 當(dāng)然別忘了 CGContext
extension CGContext {
func circleAt(center: CGPoint, radius: CGFloat) {
arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
}
}
看似挺好,但是注意TestRender和 CGContext的circleAt
實(shí)現(xiàn)僅僅是調(diào)用arcAt
方法罷了跪腹,說(shuō)白了就是復(fù)制代碼褂删,顯然不可取。
這時(shí)我們要用到Swift中的Protocol Extension 特性拉冲茸,為協(xié)議加上默認(rèn)實(shí)現(xiàn)屯阀,這次我們是對(duì)Renderer協(xié)議進(jìn)行Extension缅帘,加上默認(rèn)的circleAt實(shí)現(xiàn):
extension Renderer {
// 這意味著所有遵循Renderer協(xié)議的對(duì)象都具有一個(gè)circleAt默認(rèn)實(shí)現(xiàn)方法
func circleAt(center: CGPoint, radius: CGFloat) {
arcAt(center, radius: radius, startAngle: 0, endAngle: twoPi)
}
}
這樣所有只要遵循了Renderer協(xié)議的對(duì)象都具有circleAt實(shí)現(xiàn)方法拉!
接著我們?cè)贋?Renderer 協(xié)議Extension一個(gè)默認(rèn)實(shí)現(xiàn)方法:rectangleAt(edges: CGRect)
难衰。ps:強(qiáng)調(diào)這里并未在Renderer協(xié)議中添加rectangleAt
聲明钦无,而是在Extension中添加了默認(rèn)實(shí)現(xiàn)方法,這很重要盖袭。
現(xiàn)在有如下代碼:
// 對(duì)協(xié)議進(jìn)行extension 是為其實(shí)現(xiàn)默認(rèn)方法
extension Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectangleAt(edges: CGRect) { ... }
}
// 對(duì)類(lèi)進(jìn)行extension協(xié)議 是表明遵循協(xié)議 兩者有本質(zhì)區(qū)別
extension TestRenderer : Renderer {
func circleAt(center: CGPoint, radius: CGFloat) { ... }
func rectangleAt(edges: CGRect) { ... }
}
let r = TestRenderer()
// 1
r.circleAt(origin, radius: 1);
// 2
r.rectangleAt(edges);
- 調(diào)用的自然是TestRenderer的circleAt方法
- 調(diào)用的自然是TestRenderer的rectangleAt方法
現(xiàn)在修改let r : Renderer = TestRenderer()
失暂,編譯器只知道r是Renderer類(lèi)型,而非TestRenderer
鳄虱,此時(shí)1弟塞、2調(diào)用的是誰(shuí)的方法呢?ps:swift以后要有筆試題 這絕壁會(huì)有拙已!
- 調(diào)用TestRenderer的circleAt方法决记,因?yàn)檫@在Renderer協(xié)議中是required的
- 調(diào)用Renderer的默認(rèn)實(shí)現(xiàn)方法rectangleAt。
表示現(xiàn)在略感迷茫悠栓。-.-! 之后補(bǔ)充吧霉涨。
關(guān)于協(xié)議的遵循:
extension CollectionType {
public func indexOf(element: Generator.Element) -> Index? {
for i in self.indices {
// 報(bào)錯(cuò)信息: binary operator '==' cannot be applied two Generator.Element operands
if self[i] == element {
return i
}
}
return nil
}
}
道理我都懂按价,使用 '==' 操作符進(jìn)行前要確認(rèn)兩邊對(duì)象怎么樣才算相等(返回true)惭适,怎樣算不相等(返回false)。舉例來(lái)說(shuō)楼镐,Int類(lèi)型數(shù)據(jù) 2 和 3 進(jìn)行比較癞志,顯然不相等嘍;String類(lèi)型數(shù)據(jù)“PMST” 和 “PMST” 比較是相等的嘍框产,那如果是個(gè)自定義類(lèi)型MyClass的實(shí)例對(duì)象 a 和 b 進(jìn)行比較呢凄杯? 怎么樣才算相等? 這個(gè)比較行為需要我們來(lái)自定義對(duì)吧秉宿,我的地盤(pán)我做主戒突!
而這里我們是在為CollectionType 協(xié)議實(shí)現(xiàn)一個(gè)默認(rèn)方法IndexOf,只需要加個(gè)約束即可:
extension CollectionType where Generator.Element : Equatable {
public func indexOf(element: Generator.Element) -> Index? {
for i in self.indices {
if self[i] == element {
return i
}
}
return nil
}
}
先講到這里描睦,更多內(nèi)容請(qǐng)見(jiàn)官方Video膊存。
Why Coding Like This? 詳述Ordered一例
Binary search 二分法不是本文的重點(diǎn),因此不會(huì)這里不會(huì)詳述忱叭,但是你可以簡(jiǎn)單先看下C語(yǔ)言實(shí)現(xiàn)原理隔崎。
首先認(rèn)識(shí)幾個(gè)問(wèn)題:
- 要使用二分法查找的數(shù)組一定是有序的!
- 數(shù)組元素類(lèi)型可以是Int韵丑,Double爵卒,String等!
實(shí)現(xiàn)myBinarySearch
函數(shù)撵彻,函數(shù)傳入已經(jīng)排序好的數(shù)組sortedKeys
以及要查找的鍵值k
钓株,返回鍵值的索引值实牡。譬如傳入[2,3,4,5,6,7]
和key = 7
,那么返回索引號(hào)為5享幽。
先以Int數(shù)據(jù)類(lèi)型為例:
func myBinarySearch(sortedKeys:[Int],forKey k : Int) -> Int{
var lo = 0,hi = sortedKeys.count
while hi > lo{
let mid = lo + (hi - lo) / 2
if sortedKeys[mid] < k {lo = mid + 1}
else{ hi = mid}
}
return lo
}
myBinarySearch([2,3,4,5,6], forKey: 4) // 索引值為2
考慮到數(shù)組元素類(lèi)型還有Double String等等铲掐,所以使用泛型來(lái)實(shí)現(xiàn):
func myBinarySearch<T>(sortedKeys:[T],forKey k : T) -> Int{
var lo = 0,hi = sortedKeys.count
while hi > lo{
let mid = lo + (hi - lo) / 2
// 報(bào)錯(cuò)
if sortedKeys[mid] < k {lo = mid + 1}
else{ hi = mid}
}
return lo
}
很遺憾,編譯器報(bào)錯(cuò)了“Binary operator'< cannot ...'”值桩,主要是傳入的泛型是否具有比較性無(wú)從得知摆霉,因此出現(xiàn)了報(bào)錯(cuò)。
我們只需要為泛型T加上Comparable約束奔坟,問(wèn)題迎刃而解携栋。
func myBinarySearch<T:Comparable>(sortedKeys:[T],forKey k : T) -> Int{
var lo = 0,hi = sortedKeys.count
while hi > lo{
let mid = lo + (hi - lo) / 2
if sortedKeys[mid] < k {lo = mid + 1}
else{ hi = mid}
}
return lo
}
myBinarySearch([2,3,4,5,6], forKey: 4)
是時(shí)候加些面向協(xié)議的“佐料”了。有序即具有先后之分咳秉,那么對(duì)象自身(self)和其他(other)必須有個(gè)排序問(wèn)題婉支,因此協(xié)議制定了 func precedes(other:Self)->Bool
協(xié)議方法,用于判斷對(duì)象自身和其他對(duì)象的排序澜建。
protocol Ordered{
func precedes(other:Self) -> Bool
}
// 報(bào)錯(cuò)
func binarySearch(sortedKeys:[Ordered],forKey k: Ordered) -> Int{
var lo = 0,hi = sortedKeys.count
while hi > lo{
let mid = lo + (hi - lo) / 2
if sortedKeys[mid].precedes(k){lo = mid + 1}
else{ hi = mid}
}
return lo
}
這里同樣有個(gè)問(wèn)題“protocol 'Ordered' can only be used as a generic constraint because it has Self or associated type requirements”向挖,前文已經(jīng)提及,改動(dòng)方法是使用泛型+約束炕舵。
現(xiàn)在完整代碼如下:
protocol Ordered{
func precedes(other:Self) -> Bool
}
func binarySearch<T : Ordered>(sortedKeys:[T],forKey k: T) -> Int{
var lo = 0,hi = sortedKeys.count
while hi > lo{
let mid = lo + (hi - lo) / 2
if sortedKeys[mid].precedes(k){lo = mid + 1}
else{ hi = mid}
}
return lo
}
滿(mǎn)心歡喜去測(cè)試何之,結(jié)果顯然很悲傷:
// 報(bào)錯(cuò)cannot invoke binarySearch with an argument...
let position = binarySearch(["2", "3", "5", "7"], forKey: "5")
原因很簡(jiǎn)單,函數(shù)要求傳入的參數(shù)是遵循Ordered協(xié)議的類(lèi)型T數(shù)組咽筋,而String顯然沒(méi)有遵循Ordered協(xié)議溶推。因此我們接下來(lái)要做的是extension String:Ordered{}
,顯然除了String奸攻,還有Int等蒜危,一并寫(xiě)了?
// 以下擴(kuò)展的作用是遵循Ordered協(xié)議
extension Int : Ordered {
func precedes(other: Int) -> Bool { return self < other }
}
extension String : Ordered {
func precedes(other: String) -> Bool { return self < other }
}
// 其他...
難道為每一個(gè)類(lèi)型都進(jìn)行extension實(shí)現(xiàn)嗎睹耐?顯然有更好的方法辐赞,還記得前文講得協(xié)議擴(kuò)展嗎?為Comparable
協(xié)議增加默認(rèn)實(shí)現(xiàn)硝训!當(dāng)然Int朵逝,String類(lèi)型逃脫不了遵循Ordered的命運(yùn)(因?yàn)楹瘮?shù)傳入的必須是實(shí)現(xiàn)Ordered協(xié)議的對(duì)象)狮杨,但是真的節(jié)省了很多重復(fù)的代碼,不是嗎?
extension Comparable {
func precedes(other: Self) -> Bool { return self < other }
}
extension Int : Ordered {}
extension String : Ordered {}
這里的亮點(diǎn)是為已有協(xié)議Comparable
進(jìn)行Extension喉祭,添加了precedes
的默認(rèn)實(shí)現(xiàn)汞贸。這也意味著所有只要遵循了Comparable
協(xié)議的已有類(lèi)型,同樣也是遵循Ordered
協(xié)議的彰导。但是倘若你沒(méi)有使用extension Int:Ordered{}方式明確告知Int類(lèi)型實(shí)現(xiàn)Ordered協(xié)議的話(huà)敲茄,那么即使Int類(lèi)型擴(kuò)展中實(shí)現(xiàn)有precedes方法,也不能說(shuō)Int類(lèi)型遵循Ordered協(xié)議堰燎!
舉個(gè)例子吧:
protocol myProtocol{
func sayHello()
}
class MyClass{
func sayHello(){
print("hello")
}
}
let mc = MyClass()
mc is myProtocol // false
盡管MyClass實(shí)現(xiàn)了協(xié)議的要求,但是它沒(méi)有告知我遵循了myProtocol協(xié)議笋轨,所以很遺憾秆剪,然并卵。
回到先前的話(huà)題:
extension Comparable {
func precedes(other: Self) -> Bool { return self < other }
}
extension Int : Ordered {}
extension String : Ordered {}
let truth = 3.14.precedes(98.6) // 編譯通過(guò)
Double
類(lèi)型遵循Comparable
協(xié)議爵政,因此也具有precedes
方法仅讽!所以可以調(diào)用precedes
方法,貌似這不是我們期望的<匦洁灵! 更糟糕的是Double
并未遵循Ordered
協(xié)議!因此以下調(diào)用是錯(cuò)誤的掺出!
let position = binarySearch([2.0, 3.0, 5.0, 7.0], forKey: 5.0)//報(bào)錯(cuò)
當(dāng)然我們可以繼續(xù)使用extension Double:Ordered{}
來(lái)解決問(wèn)題徽千,但是說(shuō)實(shí)話(huà)真的不咋地,是時(shí)候思考下了汤锨!
為何要給Comparable
進(jìn)行extension
双抽,為何不是給Ordered
協(xié)議進(jìn)行呢?就像這樣:
extension Ordered {
//報(bào)錯(cuò)
func precedes(other: Self) -> Bool { return self < other }
}
什么泥畅? 無(wú)法比較self
和other
荠诬?加個(gè)約束唄琅翻!
extension Ordered where Self : Comparable {
func precedes(other: Self) -> Bool { return self < other }
}
extension Double:Ordered{}
還記得Self
嗎位仁?其實(shí)就是遵循協(xié)議的類(lèi)型的placeholder罷了!
再來(lái)看看 let truth = 3.14.precedes(98.6)
調(diào)用方椎,已經(jīng)編譯報(bào)錯(cuò)了聂抢,確實(shí)本就不應(yīng)該能夠調(diào)用,去掉它吧棠众!
最后美化下所寫(xiě)的二分法代碼琳疏,你可以寫(xiě)成兩種方式
方式一:全局函數(shù)
func binarySearch<
C : CollectionType where C.Index == RandomAccessIndexType,
C.Generator.Element : Ordered
>(sortedKeys: C, forKey k: C.Generator.Element) -> Int {
...
}
// 注意調(diào)用方式
let pos = binarySearch([2, 3, 5, 7, 11, 13, 17], forKey: 5)
方式二:擴(kuò)展已有協(xié)議
extension CollectionType where Index == RandomAccessIndexType,
Generator.Element : Ordered {
func binarySearch(forKey: Generator.Element) -> Int {
...
} }
// 注意調(diào)用方式
let pos = [2, 3, 5, 7, 11, 13, 17].binarySearch(5)
相比較我更喜歡后者!