17. %d\n Xcode Study — About Design Pattern 由繁入簡(三)

春麗 S.T.E.M.
7 min readAug 22, 2021

--

Dependency Injection / Low Coupling / Singleton? / auto layout

如果把亂亂的左圖整理後,就會看到權責分明的右圖。

Model:Data Structure、Data Manipulation

View:UI

Controller:Life Cycle、Setup(View / Animation)、UI Navigation、API call & biz logic、Data Convision、Data Source & Delegate⋯⋯。

我們可以很容易看出C肥大的原因,我自己感覺vc如果拉了許多outlet,這些outlet也還是必須的,能夠縮減肥大的方法就好比將同種類的outlet,或同種類的action拉在一起,這其中tableviewcell,我還是習慣將它與vc放在同個檔案,除非extension vc為某某datasource跟某某delegate也想要另外放在一個檔案裡。

這邊先將Network放到model裡,減輕了vc的負擔,卻能做到相同效果,vc裡面這樣,就有點像是把API call拉出來交給別人;那在Model裡有沒有類似的例子呢?有的。

除了(ㄧ) 提到的下面Model裡的這個屬性,是讓其他屬性來定義它的內容,就不是全在vc裡面去控制形成我們所需的字串。

var description: String {
return """
Title: \(title)
Details: \(details)
Date: \(dateString)
Session Number: \(sessionNumber)
"""
}

另外,若Model裡是這樣宣告

class Session: CustomStringConvertible, Decodable {
//
// MARK: - Variables And Properties
//

var date: Date

var dateString: String {
return Constants.DateFormatters.simpleDateFormatter.string(from: date)
}

雖然解析時我們有date,但是最終顯示不能直接丟date上去,所以新增了另個屬性,調用了另個swift file的Constants這個struct

struct Constants {
//
// MARK: - Date Formatters
//
struct DateFormatters {
static let simpleDateFormatter: DateFormatter = {
var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
return dateFormatter
}()
}
}

這就是data manipulation!常見的如字串浮點數時間等的處理。

耦合(Coupling)

什麼是耦合?其實storyboard裡有個簡單的例子可以說明,如果今天我複製一個專案裡,storyboard的架構到另外一個專案,雖然可以看到vc是vc,ui element是ui element,可是這時沿用的module是前一個專案,如果不幸地,新增的cocoa touch class名稱又取的跟前專案一樣,因為你想說這樣就不需要再重新選取繼承,這樣卻大錯特錯了,把上面的元件與程式碼連結後,回到storyboard裡,竟然同個元件拉了兩個outlet,其實應該這樣說,兩個元件共用一個outlet,這時Xcode會不明白要顯示哪個元件。

程式碼裡的互相拉扯,就像是這樣的例子,我們通常希望程式碼不要打架吧,不是嗎?為此,就會有一些解決辦法,例如DI(Dependency Injection)。

這邊讓我突然想到Singleton,所謂的單例設計模式,singleton為了保有單一唯一,而設計成跟程式共存亡,只有單一唯一,不會另生成實體,如此便不會跟人互相拉扯,但其實不是這樣的,先扯開一下話題,在傳值的時候,我們通常不建議使用KVC、KVO,或是UserDefaults,因為這個值會不好追蹤何時,或由誰改變的;回到單例,它雖然不會另生成實體,可是它在各處被改變,就好像全域變數一樣赤身裸體,就因為這樣,它反而容易耦合,有寫過簡易計算機的朋友都知道,AC鍵是清空用,如果一個變數會重複使用,但又想管理它的生命週期,例如我前一秒輸入的數字,在什麼時候要被儲存,在什麼時候應該清掉它,那很簡單,只要不使用它的地方都設置清空不就得了?那麼,你將落入耦合的陷阱,當赤身裸體的變數到處跑,你不容易確定哪邊用不到它,當你以為都設置清空是好事,結果卻會計算錯誤,這在練習寫計算機算是初學程式設計常見問題。

再回到storyboard來舉例,如果我今天使用auto layout,四個元件擺在同一行,我用的不是stack view( vertical / horizontal ),而是一般的固定三方向及元件大小,那麼,今天UI設計師想要再加入一個元件進來,卻還沒決定它的順序時,你該怎麼辦?做多種排列順序的auto layout?這時才會想到stack view的好,那一般的auto layout就像是在storyboard的耦合,互相依賴與牽制,很難解耦(消除這個耦合)。

所以,在程式碼裡,我們也是希望程式碼與程式碼屬low coupling,即是低耦合的。

會講到這裡,主要還是因為vc的肥大,在還沒有storyboard時,會由appdelegate去創造root vc,當storyboard出現後,便可都由vc控制,所以就越來越肥大了,這篇不會講到coordinator,不過要知道,design pattern,MVP、MVVM、VIPER都是因為vc的肥大,而將權責分出來,設計模式應該是一種態度吧,怕眼花撩亂的態度,哈。

關於分權責,是因為不希望過度依賴,然而把權責交給別人,不代表減少依賴,這在appcoda的文章看到一個簡單例子

class Dog {
func bark() { print("wroof!")}
}class Master {
var pet = Dog()
func pokePet() { pet.bark() }
}

如果今天我想改bark( )的名字,例如改成woof( ),雖然Xcode可以refactor很方便,但是程式碼裡面如果有很多地方都必須改,那也很可怕,表示牽一髮動全身,這還只是改方法名,如果動的是型別,同時又有例如抓取網路資料時的解析,那將不只是改改名這麼簡單。

這個例子,把燙手山芋交給別人是否就OK了呢?

class Master {
var pet: Dog
init(pet: Dog) { self.pet = pet }
func pokePet() { pet.woof() }
}

雖然已不是內部直接生成實體了,仍然要為了改名而苦惱,看到這邊就知道要怎麼擺脫依賴了吧?用protocol。

protocol Pet { func wasPoked() }class Master {
var pet: Pet
init(pet: Pet) { self.pet = pet }
func pokePet() { pet.wasPoked() }
}extension Dog: Pet {
func wasPoked() { woof() }
}此時生成Master實體
let master = Master(pet: Dog())
master.pokePet()
它就會呼叫狗的wasPoked() 為woof()

如此無論改成什麼名字,只要改Dog遵循Pet協定後,實作的方法,即可應用到master身上。

再往下想,此時還可讓其他寵物遵循Pet協定,如此生成不同型別的實體,都可對應到該型別實作的方法。

既然如此,就該想到泛型,留待下篇。

--

--

春麗 S.T.E.M.
春麗 S.T.E.M.

Written by 春麗 S.T.E.M.

Do not go gentle into that good night, Old age should burn and rave at close of day; Rage, rage, against the dying of the light.

No responses yet