Swift -POP( 面向協議編程)與OOP(面向對象編程)


本人已遷移博客至掘進,以后會在掘進平台更新最新的文章也會有更多的干貨,歡迎大家關注!!!https://juejin.im/user/588993965333309

 

面向協議編程(Protocol Oriented Programming,簡稱POP),是Swift的一種編程范式,Apple於2015年WWDC提出的,如果大家看Swift的標准庫,就會看到大量POP的影子。

同時Swift也是一門面向對象的編程語言(Object Oriented Programming,簡稱OOP),在Swift開發中,OOP和POP是相輔相成的,任何一方並不能取代另一方。

 今天我們重點講解下面向協議編程(POP)在Swift下的使用

回顧OOP

OOP的三大特性:封裝、繼承、多態

繼承的經典使用場合

當多個類(比如A、B、C類)具有很多共性時,可以將這些共性抽取到一個父類(比如D類),最后A、B、C類繼承D類

OOP的不足

但有些問題,使用OOP並不能很好的解決問題,比如如何將BVC、DVC的公共方法run抽取出來?

class BVC: UIViewController{
    func run() {
        print("run")
    }
}

class DVC: UITableViewController{
    func run() {
        print("fun")
    }
}

基於OOP想到的一些解決方案?

  1. 將run方法放到另一個對象A中,然后BVC、DVC擁有A屬性--多了一些額外的依賴關系
  2. 將run方法增加到UIViewController分類中--UIViewController會越來越臃腫,而且會影響它的其它所有子類
  3. 將run方法抽取到新的父類,采用多繼承?Swift不支持多繼承-(C++支持多繼承)

 

POP的解決方案

protocol Runnable {
    func run()
}

extension Runnable {
    func run() {
        print("run")
    }
}

class BVC: UIViewController, Runnable{}
class DVC: UITableViewController, Runnable{}

POP的注意點

  • 優先考慮創建協議,而不是父類(基類)
  • 優先考慮值類型(struct, enum),而不是引用類型(class)
  • 巧妙利用協議拓展功能
  • 不要為了面向協議而實用協議

 

實例-使用協議實現前綴效果

統計字符串有幾個數字出現,例如1234dafdaf1234,應該返回數字8?

1、如果在平常一處用到,可能大家直接會寫一個對象方法,然后調用一下,即可計算出,如下:

func numberCount(_ str: String) -> Int {
    var count = 0
    for c in str where ("0"..."9").contains(c) {
        count += 1
    }
    return count
}

通過調用self.numberCount("1234dafdaf1234")得出結論

 2、通過上面需求,可以得出是字符串的拓展功能,因此想到另外一種對字符串進行拓展extension String,計算屬性相當於方法

extension String {
    ///計算屬性===方法,下面兩種完全等價
    //方法
    func numberCount() -> Int {
        var count = 0
        for c in self where("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
    //計算屬性
    var numberCount1: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
}
print("1234dafdaf1234".numberCount())

3、通過上面的已經可以很好的解決問題啦,對於要求很高的團隊,上面還是有一些問題的,比如,上面是對系統的String進行拓展,如果定義的計算屬性和系統的屬性名字相同咋辦,針對這個問題,可以有,在numberCount前面加入項目簡稱----(計算屬性的本質是方法,要與存儲屬性分別開,不能為extension添加存儲屬性,如果要想添加,建立關聯添加)

extension String {
    ///計算屬性===方法,下面兩種完全等價
    //方法
    func hcc_NumberCount() -> Int {
        var count = 0
        for c in self where("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
    //計算屬性
    var hccNumberCount1: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
}
print("1234dafdaf1234".hccNumberCount1)

可以解決上面遇到的問題,但是不夠優雅,也不符合Swift的風格,Swift中有大量協議的使用,如果有協議的加入,可以比較優雅的實現

是否可以實現"1234dafdaf1234".hcc.numberCount?

(因為是“”.hcc,所以對字符串拓展了一個屬性hcc,而hcc.numberCount又是hcc類的一個屬性,所以如下)

struct HCC {
    var string: String
    init(_ str: String) {
        self.string = str
    }
    var numberCount: Int {
        var count = 0
        for c in string where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

extension String {
    var hcc: HCC {return HCC(self)}//傳值self字符串
}

print("1234dafdaf1234".hcc.numberCount)

4、上面已經完成對字符串的拓展功能的系列,也已經很好的很優雅的解決了問題,但是如果相對字符串拓展一個功能的話,這就OK啦

如果想對數組進行拓展一個類似的方法,還要在HCC里面增加array屬性和初始化以及拓展Array功能,就會發現冗余代碼太多,且不夠封裝,不夠通用

這時候泛型的作用就來啦,如下

struct HCC<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension String {
    var hcc: HCC<String> {HCC(self)}
}

class Person{}
extension Person {
    var hcc: HCC<Person> {HCC(self)}
}

extension HCC where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension HCC where Base == Person {
    func run() {
        print("run")
    }
}

"1234dafdaf1234".hcc.numberCount
Person().hcc.run()

效果圖如下

 5、上面實現了通過類的對象調用,可不可以實現通過類本身來調用呢,因為我在使用類.hcc的時候,並不想出現類對象的屬性,只想出現類型本身的方法和屬性,這就需要用到static來修飾

struct HCC<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension String {
    var hcc: HCC<String> {HCC(self)}
    static var hcc: HCC<String>.Type {HCC<String>.self}
}

class Person{}
extension Person {
    var hcc: HCC<Person> {HCC(self)}
}

extension HCC where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    
    static func test() {
        print("test")
    }
}

extension HCC where Base == Person {
    func run() {
        print("run")
    }
}

"1234dafdaf1234".hcc.numberCount
Person().hcc.run()
String.hcc.test()

完成上面需求!

6、但是如果要再次增加一個Dog類,也要在Dog類中有

var hcc: HCC<String> {HCC(self)}
static var hcc: HCC<String>.Type {HCC<String>.self}

這些代碼,增加其他,會導致代碼還是會有點冗余,這樣就發現了POP的好處-是面向協議編程,將公共的地方抽出來(協議只能聲明一些東西,想擴充一些東西,就是在extension加入)

///前綴類型

struct HCC<Base> {

    var base: Base

    init(_ base: Base) {

        self.base = base

    }

}

///利用協議擴展前綴屬性

protocol HCCCompatible {}

extension HCCCompatible {

    var hcc: HCC<Self> {HCC(self)}

    static var hcc: HCC<Self>.Type {HCC<Self>.self}

}

///給字符串擴展功能

//讓String擁有前綴屬性

extension String: HCCCompatible {}

//給string.hcc以及String().hcc前綴擴展功能

extension HCC where Base == String {

    var numberCount: Int {

        var count = 0

        for c in base where("0"..."9").contains(c){

            count += 1

        }

        return count

    }

    static func test() {

        print("test")

    }

}

 

class Person{}

extension Person: HCCCompatible{}

class Dog{}

extension Dog: HCCCompatible{}

extension HCC where Base == Person {

    func run() {

        print("run")

    }

}

這樣就比較順利較完美的解決了問題,又很好的拓展了功能!!!

 

總結

以后要給某一個類擴展功能,可采取下面步驟

  1. 定義一個前綴類型(如上面的hcc等)
  2. 定義一個協議(protocol)
  3. 遵守該協議即可

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM