一、對 POP 的基礎(chǔ)認(rèn)知
1. 什么是面向協(xié)議編程?會取代面向?qū)ο缶幊虇幔?/h5>
-
面向協(xié)議編程(Protocol Oriented Programming),簡稱
POP。是 Swift 的一種編程范式 ,Apple 于 2015 年在 WWDC 提出,在 Swift 的標(biāo)準(zhǔn)庫中能見到大量 POP 的影子。
-
同時,Swift 也是一門面向?qū)ο蟮木幊陶Z言(
Object Oriented Programming,簡稱 OOP)。在 Swift 開發(fā)中,OOP 和 POP 是相輔相成的,任何一方并不能取代另一方。
POP 的出現(xiàn)能彌補 OOP 一些設(shè)計上的不足。
2. 回顧 OOP 的三大特性是什么?并說一下繼承的經(jīng)典使用場合?
- OOP 的三大特性:繼承、封裝、多態(tài)
-
繼承的經(jīng)典使用場合:當(dāng)多個類(比如 A、B、C 類)具有很多
共性時,可以將這些共性抽取到一個父類中(比如 D 類),最后 A、B、C類繼承 D 類
3. 傳統(tǒng) OOP 面臨的問題?以及它們的解決方案? (重要)
image.png
4. 如何解決上述 OOP 無法很好解決的問題?
- Swift 中可以
擴展一個協(xié)議的具體實現(xiàn),很好的解決了上述問題。
image.png
5. POP 的注意點?(說兩點即可)
- 優(yōu)先考慮創(chuàng)建協(xié)議,而不是父類(基類)
- 優(yōu)先考慮值類型(struct、enum),而不是引用類型(class)
- 巧用協(xié)議的擴展功能
- POP 和 OOP 是相輔相成的,不要強行 POP
二、優(yōu)雅的前綴
1. 優(yōu)雅前綴版本一:如何給系統(tǒng)類(String)擴展自定義(numberCount)方法?
extension String {
func numberCount() -> Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".numberCount()) // 輸出:8
- 上面的辦法雖然實現(xiàn)了
numberCount方法擴展,但是如何保證不和系統(tǒng)的辦法沖突呢?如果有一天 Apple 也給 String 添加了 numberCount方法且含義不同怎么辦?
2. 優(yōu)雅前綴版本二:不和系統(tǒng)沖突
- 從 OC 的經(jīng)驗來看,我們可以給
numberCount添加前綴,這樣就能保證不和別人沖突了。實現(xiàn)如下:
extension String {
var mj_numberCount: Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".mj_numberCount) // 輸出:8
- 但是上面做法
不夠 Swift 風(fēng)格,我們希望如下調(diào)用該方法
var str = "122abcde10294"
print(str.mj.numberCount)
- 具體實現(xiàn)如下:給
String 擴展一個 mj 計算屬性
struct MJ {
var str = ""
init(_ str: String) {
self.str = str
}
var numberCount: Int {
str.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
extension String {
var mj: MJ {
MJ(self)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
- 上面代碼依然存儲問題,①如果我們需要給 Array 也擴展 mj 呢?②如果我們需要給 Array 也擴展 numberCount 方法呢?③如果我們需要給 String 擴展 characterCount 方法呢?
- 上面的代碼就將造成我們無法很好解決上述問題,也無法有很好的擴展性
3. 優(yōu)雅前綴版本三:擴展類方法、使用泛型
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
struct Person {
var age: Int
var name: String
}
//給 String 擴展
extension String {
var mj: MJ<String> {MJ(self)}
static var mj: MJ<String>.Type {MJ.self}
}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//給 Person 擴展
extension Person {
var mj: MJ<Person> {MJ(self)}
static var mj: MJ<Person>.Type {MJ.self}
}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
String.mj.sayHello() // 輸出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 輸出:20
Person.mj.sayHello() // 輸出:Hello Person: Person
- 上述代碼就解決了給不同 類型擴展 mj 的需求,也能很好的擴展各種方法。
- 但是依然存在問題①
var mj: MJ<Person> {MJ(self)}; static var mj: MJ<Person>.Type {MJ.self} 這部分代碼是重復(fù)的代碼,可有辦法解決?②如果要擴展 mutating 修飾的方法可以嗎?
4. 優(yōu)雅前綴版本四: 使用協(xié)議(最終版)
// 定義基礎(chǔ)類,也是擴展前綴
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
protocol MJCompatible {}
// 在這里擴充 MJCompatible 的方法實現(xiàn)
extension MJCompatible{
static var mj: MJ<Self>.Type {
get {MJ<Self>.self}
set {} // 加 set 適配 mutating 方法
}
var mj: MJ<Self> {
get {MJ(self)}
set {}
}
}
struct Person {
var age: Int
var name: String
}
//給 String 擴展
extension String: MJCompatible {}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//給 Person 擴展
extension Person: MJCompatible {}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
mutating func modifyAge(age: Int) {
base.age = age
print("enter modifyAge", age, base.age)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
String.mj.sayHello() // 輸出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 輸出:20
person.mj.modifyAge(age: 10)
// (下面沒有輸出 10,因為MJ(self)值傳遞,如果把 Person 從 struct 改成 class,就會輸出 10)
print(person.age) // 輸出:20
Person.mj.sayHello() // 輸出:Hello Person: Person
5. 利用協(xié)議實現(xiàn)類型判斷(了解)
image.png
POP。是 Swift 的一種編程范式 ,Apple 于 2015 年在 WWDC 提出,在 Swift 的標(biāo)準(zhǔn)庫中能見到大量 POP 的影子。Object Oriented Programming,簡稱 OOP)。在 Swift 開發(fā)中,OOP 和 POP 是相輔相成的,任何一方并不能取代另一方。POP 的出現(xiàn)能彌補 OOP 一些設(shè)計上的不足。繼承的經(jīng)典使用場合?共性時,可以將這些共性抽取到一個父類中(比如 D 類),最后 A、B、C類繼承 D 類
image.png
擴展一個協(xié)議的具體實現(xiàn),很好的解決了上述問題。
image.png
extension String {
func numberCount() -> Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".numberCount()) // 輸出:8
numberCount方法擴展,但是如何保證不和系統(tǒng)的辦法沖突呢?如果有一天 Apple 也給 String 添加了 numberCount方法且含義不同怎么辦?numberCount添加前綴,這樣就能保證不和別人沖突了。實現(xiàn)如下:extension String {
var mj_numberCount: Int {
self.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
print("122abcde10294".mj_numberCount) // 輸出:8
不夠 Swift 風(fēng)格,我們希望如下調(diào)用該方法var str = "122abcde10294"
print(str.mj.numberCount)
String 擴展一個 mj 計算屬性
struct MJ {
var str = ""
init(_ str: String) {
self.str = str
}
var numberCount: Int {
str.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
}
extension String {
var mj: MJ {
MJ(self)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
struct Person {
var age: Int
var name: String
}
//給 String 擴展
extension String {
var mj: MJ<String> {MJ(self)}
static var mj: MJ<String>.Type {MJ.self}
}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//給 Person 擴展
extension Person {
var mj: MJ<Person> {MJ(self)}
static var mj: MJ<Person>.Type {MJ.self}
}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
String.mj.sayHello() // 輸出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 輸出:20
Person.mj.sayHello() // 輸出:Hello Person: Person
var mj: MJ<Person> {MJ(self)}; static var mj: MJ<Person>.Type {MJ.self} 這部分代碼是重復(fù)的代碼,可有辦法解決?②如果要擴展 mutating 修飾的方法可以嗎?// 定義基礎(chǔ)類,也是擴展前綴
struct MJ<Base> {
var base: Base
init(_ base: Base) {
self.base = base
}
}
protocol MJCompatible {}
// 在這里擴充 MJCompatible 的方法實現(xiàn)
extension MJCompatible{
static var mj: MJ<Self>.Type {
get {MJ<Self>.self}
set {} // 加 set 適配 mutating 方法
}
var mj: MJ<Self> {
get {MJ(self)}
set {}
}
}
struct Person {
var age: Int
var name: String
}
//給 String 擴展
extension String: MJCompatible {}
extension MJ where Base == String {
var numberCount: Int {
base.reduce(0) {("0"..."9").contains($1) ? $0 + 1 : $0}
}
static func sayHello() { print("Hello String: ", Base.self)}
}
//給 Person 擴展
extension Person: MJCompatible {}
extension MJ where Base == Person {
var numberCount: Int {
return base.age
}
static func sayHello() { print("Hello Person: ", Base.self)}
mutating func modifyAge(age: Int) {
base.age = age
print("enter modifyAge", age, base.age)
}
}
var str = "122abcde10294"
print(str.mj.numberCount) // 輸出:8
String.mj.sayHello() // 輸出:Hello String: String
var person = Person(age: 20, name: "carrot")
print(person.mj.numberCount) // 輸出:20
person.mj.modifyAge(age: 10)
// (下面沒有輸出 10,因為MJ(self)值傳遞,如果把 Person 從 struct 改成 class,就會輸出 10)
print(person.age) // 輸出:20
Person.mj.sayHello() // 輸出:Hello Person: Person

image.png