在代碼庫中建立可靠的結構通常是必不可少的,以便更容易使用。然而,實現一個既足夠嚴格以防止錯誤和問題的結構 - 以及對現有功能足夠靈活的結構以及我們想要的任何未來變化 - 都可能非常棘手。
對於模型代碼而言尤其如此,模型代碼通常由許多不同的功能使用,每個功能都有自己的一組要求。本週,讓我們來看看構建核心模型的數據的幾種不同技術,以及如何改進該結構對我們的其餘代碼庫產生重大積極影響。
同時小編這裏有些書籍和麪試資料哦(點擊下載)
形成層次結構
在項目開始時,模型通常可以保持非常簡單。由於我們尚未實現許多功能,因此我們的模型很可能不需要包含太多數據。然而,隨着我們的代碼庫的增長,我們的模型經常發生變化 - 並且很容易達到一個簡單的模型最終成爲各種相關數據的“全能”的程度。
例如,假設我們正在構建一個電子郵件客戶端,它使用Message
模型來跟蹤每條消息。最初,該模型可能只包含給定消息的主題行和正文,但此後逐漸增長爲包含各種其他數據:
struct Message {
var subject: String
var body: String
let date: Date
var tags: [Tag]
var replySent: Bool
let senderName: String
let senderImage: UIImage?
let senderAddress: String
}
雖然爲了呈現消息需要所有上述數據,但是直接將其保留在Message
類型本身中會使事情變得有點混亂 - 並且很可能使消息更難以使用,尤其是當我們創建新實例時 - 撰寫新郵件時或編寫單元測試時。
緩解上述問題的一種方法是將數據分解爲多個專用類型 - 然後我們可以使用它們來形成模型層次結構。例如,我們可能會將有關消息發送者的所有數據提取到Person
結構中,並將所有元數據(例如消息的標記和日期)提取到Metadata
類型中,如下所示:
struct Person {
var name: String
var image: UIImage?
var address: String
}
extension Message {
struct Metadata {
let date: Date
var tags: [Tag]
var replySent: Bool
}
}
現在,有了上述內容,我們可以爲我們的Message
類型提供一個更清晰的結構 - 因爲每個數據不直接作爲消息本身的一部分現在包含在更具上下文的專用類型中:
struct Message {
var subject: String
var body: String
var metadata: Metadata
let sender: Person
}
上述方法的另一個好處是,我們現在可以更容易地在不同的上下文中重用部分數據。例如,我們可以使用我們的新Person
類型來實現聯繫人列表等功能,或者允許用戶定義組 - 因爲該數據不再直接綁定到該Message
類型。
減少重複
除了用於更好地組織我們的代碼之外,可靠的結構還可以幫助減少項目中的重複。假設我們的電子郵件應用程序使用事件驅動的方法來處理不同的用戶操作 - 使用如下所示的Event
枚舉:
enum Event {
case add(Message)
case update(Message)
case delete(Message)
case move(Message, to: Folder)
}
使用枚舉來定義各種代碼需要處理的有限事件列表,這是在應用程序中建立更清晰數據流的好方法 - 但是我們當前的實現要求每個案例都包含Message
事件所針對的事件 - 領先在Event
類型本身內複製,以及在我們想要從事件的消息中提取信息時。
由於每個事件的操作都是對消息執行的,所以讓我們將兩者分開,並創建一個更簡單的枚舉類型,它將包含我們的所有操作:
enum Action {
case add
case update
case delete
case move(to: Folder)
}
然後,讓我們再次形成一個層次結構 - 這一次通過重構我們的Event
類型成爲一個包含a Action
和Message
它將被應用於的包裝器- 如下所示:
struct Event {
let message: Message
let action: Action
}
上述方法爲我們提供了兩全其美 - 處理事件現在只需要切換事件Action
,現在可以使用message
屬性直接從事件的消息中提取數據。
遞歸結構
到目前爲止,我們已經形成了層次結構,其中每個孩子和父母都是完全獨立的類型 - 但這並不總是最優雅,或最方便的解決方案。假設我們正在開發一個顯示各種內容的應用程序,例如文本和圖像,並且我們再次使用枚舉來定義每個內容 - 如下所示:
enum Content {
case text(String)
case image(UIImage)
case video(Video)
}
現在讓我們說我們希望讓用戶能夠形成一組內容 - 例如,通過創建收藏列表,或使用文件夾來組織內容。最初的想法可能是尋找一個專用Group
類型,它包含組的名稱和屬於它的內容:
struct Group {
var name: String
var content: [Content]
}
然而,儘管上述內容看起來優雅且結構合理,但在這種情況下它有一些缺點。通過引入一種新的專用類型,我們將需要單獨處理各個內容組 - 使得構建列表之類的內容變得更加困難 - 而且我們也無法輕鬆支持嵌套組。
因爲在這種情況下,一個組只不過是構造內容的另一種方式,所以讓它改爲Content
枚舉本身的第一類成員,只需爲它添加一個新的例子 - 就像這樣:
enum Content {
case text(String)
case image(UIImage)
case video(Video)
case group(name: String, content: [Content])
}
我們上面基本上做的是創建Content
一個遞歸數據結構。這種方法的優點在於我們現在可以重用我們用於處理內容的大部分相同代碼來處理組,並且我們可以自動支持任意數量的嵌套組。
例如,以下是我們如何處理顯示內容列表的表視圖的單元格選擇:
extension ListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let content = contentList[indexPath.row]
switch content {
case .text(let string):
navigator.showText(string)
case .image(let image):
navigator.showImage(image)
case .video(let video):
navigator.openPlayer(for: video)
case .group(let name, let content):
navigator.openList(withTitle: name, content: content)
}
}
}
上面我們使用導航器模式導航到新目的地。您可以在“Swift中的導航”中找到更多相關信息。
由於Content
現在是遞歸的,因此navigator.openList
在處理組時調用現在只需創建一個ListViewController
包含該組內容列表的新實例,使用戶能夠輕鬆地創建和導航任何內容層次結構,而我們只需要很少的努力。
專業模特
雖然能夠重用代碼通常是件好事,但有時最好創建一個更專業的新版本的模型,而不是嘗試在非常不同的上下文中重用它。
回到之前的電子郵件應用程序示例,假設我們希望用戶能夠保存部分撰寫的郵件草稿。而不是讓該功能處理完整的Message
實例,這需要不能用於草稿的數據 - 例如發件人的姓名或收到郵件的日期 - 讓我們創建一個更簡單的Draft
類型,我們將嵌套在Message
其他上下文中:
extension Message {
struct Draft {
var subject: String?
var body: String?
var recipients: [Person]
}
}
這樣,我們可以自由地將某些屬性作爲選項,並減少加載和保存草稿時我們需要處理的數據量 - 而不會影響我們處理正確消息的任何代碼。
結論
雖然哪種模型結構最適合每種情況,但在很大程度上取決於所需的數據類型以及數據的使用方式 - 在能夠重用代碼和不創建模型之間取得平衡太複雜,往往是關鍵。
形成清晰的層次結構 - 無論是使用專用類型還是通過創建遞歸數據結構 - 同時仍然偶爾爲特定用例創建模型的專用版本,可以在我們的模型代碼中形成更清晰的結構 - 並且像往常一樣,常量重構和小改進通常是達到目的的方式。
你怎麼看?在構建模型代碼時,您目前是否使用了本文中的一些技術,或者您是否有其他最喜歡的方法?請通過加我們的交流羣 點擊此處進交流羣 ,來一起交流或者發佈您的問題,意見或反饋。
謝謝閱讀~點個贊再走唄!🚀
原文地址 https://www.swiftbysundell.com/posts/structuring-model-data-in-swift