面向協議編程 (Protocol Oriented Programming
,如下簡稱 POP
),是蘋果在2015
年 WWDC 上提出的Swift
的一種全新理念編程範式。被譽爲能夠改變一切的編程方式。本篇只作爲記錄本身的學習過程。程序員
在學習POP
以前,先來簡單回顧一下面向對象編程: 編程
iOS
開發者都瞭解面向對象編程(
Object-oriented Programming
,如下簡稱:
OOP
)。在面向對象編程世界裏,一切皆爲對象,對象是程序的基本單元,對象把程序與數據封裝起來提供對外訪問的能力,提升軟件的重用性,靈活性和擴展性。在面向對象編程中,一般把對象的數據稱爲屬性,把對象的行爲稱爲方法。
面向對象編程包含三個重要的概念:swift
1️⃣:類:具備相同特徵和行爲的事物的抽象,如人類,動物類bash
2️⃣:對象:對象是類的實例,萬物皆對象app
3️⃣:方法:方法是對象的具體行爲框架
下面經過一個簡單的圖來看一下:ide
若是把上面圖片的內容轉換成代碼:class ZHPerson {
func eat() {
print("eat hamburg")
}
}
class ZHMan: ZHPerson {
var game:String?
func driver() {
print("Old driver")
}
override func eat() {
print("instant noodles")
}
}
class ZHWoman: ZHPerson {
var bag:String?
func shopping() {
print("Brush my card")
}
override func eat() {
print("beefsteak")
}
}
複製代碼
在上面代碼例子🌰中,子類ZHMan
和ZHWoman
共享了父類ZHPerson
的eat
方法,而這部分代碼被封裝到了父類 ZHPerson
中,這裏就用到了面向對象編程的核心思想特徵:封裝與繼承。函數
面向對象編程的三大特徵:封裝、繼承、多態。學習
✨封裝(Encapsulation)ui
經過對象來隱藏程序的具體實現細節,將數據與操做封裝在一塊兒,對象與對象之間經過消息傳遞機制實現互相通訊,具體的表現就是經過提供訪問接口實現消息的傳入傳出。 封裝經常會經過控制訪問權限來控制對象之間的互訪權限,常見的訪問權限:公有(public
),私有(private
),保護(protected
)。
封裝的意義:因爲封裝隱藏了具體的實現,若是實現的改變或升級對於使用方而言是無感知的,提升程序的可維護性;並且封裝鼓勵程序員把特定數據與對數據操做的功能打包在一塊兒,有利於應用程序的去耦。
✨繼承(Inheritance)
繼承即類之間能夠繼承,經過繼承獲得的類稱爲子類,被繼承的類爲父類,子類相對於父類更加具體化。 子類具備本身特有的屬性和方法,而且子類使用父類的方法也能夠覆蓋(重寫)父類方法,在某些語言中還支持多繼承,可是也帶來了覆蓋的複雜性。
繼承的意義:繼承是代碼複用的基礎機制
✨多態(Polymorphism)
多態發生在運行期間,即子類型多態,指的是子類型是一種多態的形式,不一樣類型的對象實體有統一接口,相同的消息給予不一樣的對象會引起不一樣的動做。
多態的意義:提供了編程的靈活性,簡化了類層次結構外部的代碼,使編程更加註重關注點分離
隨着時代的進步,國家的強盛,科技的發展,人們生活水平的提升,ZHMan
在擁有driver
方法的基礎上,也想要擁有shopping
方法,一樣的ZHWoman
在擁有shopping
方法的狀況下,加入了driver
方法,但是他們自己並不具備這些方法,那麼該怎麼獲取到這些自己並不具本,只在別的子類存在的方法呢? 在面向對象編程中,有以下幾個解決方法:
方法1️⃣:Copy & Paste
,給繼承於ZHPerson
的子類ZHMan
拷貝一份shopping
方法,很差。
方法2️⃣:基類,給基類ZHPerson
添加一個shopping
方法,這樣子類ZHMan
就具備了個shopping
功能,可是這樣會使其餘不須要shopping
功能的子類也具備了這個方法,很差。
方法3️⃣:依賴注入,經過外界傳入一個帶有 shopping
方法 的對象,用新的類型來提供這個功能。這是一個稍好的方式,可是引入額外的依賴關係,有耦合,很差。
方法4️⃣:多繼承,然而Swift
是不支持多繼承的。不過若是Swift
有多繼承的話,確實能夠從多個父類進行繼承,並將 shopping
方法 添加到合適的地方。可是此時會帶來另一個問題:菱形缺陷,即若是繼承的兩個父類都有一樣的方法,子類就很難肯定繼承的究竟是哪一個父類的方法。
以上就是面向對象編程在實際解決問題的時候所暴露出的問題,雖然經過我的技能總能解決,但感受就是不那麼幹淨利索。
拋開咱們熟悉的 swift
標準庫核心是面向協議不說,就連一些函數響應式編程框架,像RxSwift
也是面向協議的。面向協議編程究竟是什麼呢?一句話就能歸納:用協議擴展的方式代替繼承,實現代碼複用。
首先來建立一個最簡單的Swift協議:
protocol People {
var name: String {get set }
func shopping()
}
struct Student:People {
var name: String
func shopping() {
print("more money")
}
}
//調用
Student(name: "King").shopping()
複製代碼
上面👆👆👆代碼中定義名爲People
協議,包含一個name
屬性,以及一個shopping
方法的定義 ,Studet
結構體經過實現name
屬性 和 shopping
方法 來知足 People
協議。在調用時就可使用 People
中定義的方法了。
若是此時在多一個結構體Teacher,
struct Teacher:People {
var name: String
func shopping() {
print("more money")
}
}
複製代碼
此時須要再次實現shopping
方法,由於在協議裏沒有提供方法的實現, 那麼這樣一來每多一個類繼承協議,都須要再次實現shopping
方法,那麼這種操做不就和麪向對象編程中的 Copy & Paste
的解決方式有些相似了嗎?
那麼若是爲協議提供默認的實現方法,也就是協議擴展,是否是就解決了這個問題?
protocol People {
var name: String {get set }
func shopping()
}
extension People {
func shopping() {
print("more money")
}
}
struct Student:People {
var name: String
}
struct Teacher:People {
var name: String
}
//調用
Student(name: "King").shopping()
Teacher(name: "Queue").shopping()
複製代碼
經過協議拓展給協議的遵循者一些默認的方法、屬性的實現。符合協議的類型能夠提供本身的實現,也可使用默認的實現。同時也能夠在協議的擴展中添加協議中未聲明的附加方法實現,而且實現協議的任何類型均可以使用到這些附加方法。這樣就能夠給遵循協議的類型添加特定的方法。
在協議的世界中,咱們可使用面向對象編程中的繼承概念對協議進行繼承,造成協議樹。可是須要注意的是這一樣會帶來面向對象編程中的缺陷,不宜濫用。 下面在People
協議的基礎上實現Human
協議:
protocol Human:People {
var hobby: String {get set }
func driver()
}
複製代碼
接着來實現一個結構體:
struct Specialist: Human {
var hobby: String
var name: String
func driver() {
print("360km/h")
}
func shopping() {
print("100 million $")
}
}
複製代碼
一樣的在Specialist
結構體內不只要實現Human
協議的屬性和方法,也要實現People
協議的屬性和方法,這裏對這兩個協議中的方法拓展就不在一一贅述了。
協議的組合就相似於面向對象編程中的多繼承,採用多個協議的默認實現。
protocol People {
var name: String {get }
func shopping()
}
extension People {
func shopping() {
print("more money")
}
}
protocol Human {
var name: String {get }
func driver()
}
extension Human {
func driver() {
print("360km/h")
}
}
複製代碼
這裏咱們聲明協議People
和協議Human
,在協議中都聲明瞭一個name
屬性,並對其方法進行擴展。 下面咱們實現一個結構體:
struct Superman: People, Human {
var name: String
}
//
Superman(name: "Jing")
複製代碼
在Superman
結構體必須實現一個name
屬性來知足這兩個協議,若是此時,把People
協議中的name
屬性默認實現會怎麼樣?
extension People {
var name: String{
return "hahahahha"
}
func shopping() {
print("more money")
}
}
struct Superman: People, Human {
// var name: String
}
//
Superman()
複製代碼
依然能夠,甚至此時在Superman
結構體內不須要實現name
屬性,若是此時再把Human
協議中的name
屬性默認實現的話,結果就沒有那麼美好了。
這種狀況下,Superman 沒法肯定要使用哪一個協議擴展中 name屬性 的定義。在同時實現兩個或者以上含有同名屬性的協議,而且它們都提供了默認擴展實現時,咱們須要在具體的類型中明確地提供屬性實現。
struct Superman: People, Human {
var name: String
}
//
Superman(name: "King")
複製代碼
面向對象編程和麪向協議編程最明顯的區別在於程序設計過程當中對數據類型的抽象上,面向對象編程使用類和繼承的手段,而面向協議編程使用的是遵照協議的手段。面向協議編程是在面向對象編程基礎上發展而來的,而並非徹底背離面向對象編程的思想。 面向協議編程的好處在於,經過協議+
擴展實現一個功能,這樣就最大程度減小了耦合。
可能會有人以爲疑惑,那這和麪向接口編程有什麼區別?依稀記得,接口只是一種約束,而非一種實現,也就是說實現了某個接口的類,須要本身實現接口中的方法。可是Swift
爲協議提供了拓展功能,它可以爲協議中的方法提供默認實現,不須要寫重複的代碼。(我的理解,若有錯誤,還請指正,謝謝您! 最後感謝喵神)。