Swift 的存取控制機制 (Access Control)
由於本文屬於「備忘」性質,如果在閱讀過程中有遇到理解困難的話,除了可能是筆者寫作不周外,也可以參考 Swift 文件:
https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html。
存取等級 (Access Levels)
Swift 的五種存取等級中,對於開發 App 的人,大概只會用到三種,也就是 internal (預設等級)、fileprivate、private;另外兩種的 open、public 則是開發「跨模組」(例如開發一個框架、SDK 給別的開發者用) 應用時才需要考量。
沒有意義也不會出錯的 public 特性
範例一中,編譯器、Xcode 不會發出警告或錯誤,但其實 title 屬性的 public 在此例是沒效用的。因為:Movie 本身是 internal,也就是 Movie 類別本身並不允許外部模組存取,自然它的 title 特性就更是。
對於 public 的定義,Swift 和其他幾種更傳統的主流程式語言不同,對於以往學習、使用且熟悉其它們的人,在撰寫 Swift 時要留意這點。
子類別不可以比父類別開放
也就是不能透過繼承的方式,讓子類別對外曝露原本父類別不願對外透露的訊息。
範例二中,C1 類別無法在模組外被存取,但 C2 試圖開放。不行。
範例三中,C1 只想對同一檔案中的其他類別開發存取,但是 C2 試圖開放給其他檔案中的類別也能存取。也不行。
同樣的例子的話,Java 則沒有這個限制。在 Java 裡,若父類別不使用 final 禁止繼承,或是不把所有不想對外公開的 attributes / methods 都設定成 private 的話,子類別都可以自行曝露出去。
如果是「父類別是別人提供的」、「子類別是自己寫的」,那當然感覺「很自由」;若「父類別是自己提供的」、「子類別是別人寫的」,感覺就有點「危險/意外」了。
也就是在 Java …防兒子和防外人沒兩樣…。
子類別裡的成員可以比父類別開放,而且不可以更封閉
這點符合里氏替換原則 (Liskov Substitution principle)。用擬人白話來說的話,就是「父親對外的承諾,做兒子的只能遵守或投報更多」。對於父類別的 client 來說,在 runtime 透過注入把 instance 換成子類別的物件時,要能 work。
在 Java 中,對於 method 的 override 行為也是這樣,但是對 attribute 卻沒有限制:子類別的同名屬性可以比父類別的更開放。不過這是因為:在 Java 裡,attribute 沒有 override 的行為。事實上感覺也沒必要:若值相同就用父類別物件有的,若值不同就子類別物件自己存一份。
但是在 Swift 中,property 的能力比 Java 的 attribute 複雜:property 有觀察者 (Observers) 機制、computed (get/set) 機制,而子類別的 property 可以在這些行為上與父類別定義的 property 不同,這也是上圖範例五中為何要在 prop 特性裡加上 didSet{} 宣告的原因 (不加上這個看似無意義的程式碼的話,Xcode 是會警告出錯的哦。)。所以在 Swift 中 property 在繼承關係上有 override 的機制是有意義的。
Property 的讀寫存取可以不同等級
Swift 裡 property 可以在 get 和 set 兩者分別宣告不同的存取等級。如下:
Sub 子類別對 Super 父類別的 prop1 特性進行 override,意圖在 willSet 時觸發不同於 Super 父類別 prop1 特性不同的行為。這裡的 override 還開放了 Sub 子類別 prop1 的 get 等級成為 internal,但是保持了 set 等級為 fileprivate。
Property! 來和 Java 做個比較
Java 的「成員變數 (data member)」稱之為「attribute」,而以 private 宣告、並加上其對應的 get 和 set 方法後,三者 (attribute + get 方法+ set 方法) 才滿足 property 的定義。(property 可以用來表現 attribute,但是它不是 attribute)
參考:https://www.uml-diagrams.org/property.html
在 Swift 中,由於語法支援已經包括 get/set (computed property)、observers (可以針對 willSet / didSet 的發生時機進行控制)、lazy loading、property wrapper…等機制,所以符合完整的 property 概念,當然在使用上就更為精細了。