Swift 中的設計模式 #3 外觀模式與介面卡模式

SwiftGG翻譯組發表於2018-11-29

作者:Andrew Jaffee,原文連結,原文日期:2018-09-04譯者:鄭一一;校對:BigNerdCodingpmstForelax;定稿:Forelax

本文是我的設計模式系列教程的第三篇。在第一篇文章中,我介紹了 建立型模式中的工廠模式單例模式。在第二篇文章中,又討論了一下 行為型模式中的觀察者模式備忘錄模式

在本文中,我會就結構型模式中的外觀模式介面卡模式分別舉一個例子。首先,我建議你先去閱讀前面提到的兩篇文章,這會有助於你更熟悉軟體設計模式的一些概念。除了簡短地介紹一下設計模式的組成,我不會再重複介紹所有關於設計模式的概念了。如果需要了解,都可以在前面寫的 第一篇第二篇 中找到。

接下來的幾節,我們先來簡單回顧一下設計模式的通用概念。“Gang of Four” (“GoF”) Erich Gamma,Richard Helm,Ralph Johonson,和 John Vlissides 在他們“設計模式:物件導向軟體設計複用的基本原理”的重要著作裡整理了 23 種經典的設計模式。今天我們重點關注的是兩種結構型設計模式:外觀模式介面卡模式

值語義的面向協議程式設計

你可能會發現世面上非常多設計模式教程的示例程式碼,仍然是基於物件導向程式設計原則(OOP)、引用語義和 引用型別(classes)編寫的。所以,我決定編寫一套基於 面向協議程式設計原則(POP)、值語義和 值型別(structs)的設計模式系列教程。如果你已經看過了我之前寫的兩篇文章,我希望你還能夠熟悉一下 OOP 和 POP,引用語義和值語義這些概念。如果你還不是特別熟悉,我強烈建議趕緊去了解一下這些主題。本文所舉的例子是全部基於 POP 和值語義的。

設計模式

設計模式是開發者用於管理軟體複雜性極其重要的工具。作為常見的模板技術,它很好地對軟體中類似的、重複出現的、容易識別的問題進行了概念化抽象。我們可以將它視作最佳實踐,從而應用到日常中會遇到的那些程式設計場景中。舉一個具體的例子,回想一下你在平常寫程式碼過程中有多少次會使用或寫了遵守 觀察者設計模式 的程式碼吧。

在觀察者模式中,被觀察者(一般來說是一個關鍵資源)會給所有依賴於自己的觀察者,廣播通知其內部狀態的變化。觀察者必須告知被觀察者自己想接收通知,換句話說,觀察者必須訂閱通知。使用者授權的 iOS 彈窗推送通知,就是一個典型的觀察者模式的例子。

設計模式的分類

GoF 將 23 種設計模式歸納為三種型別,分別是“建立型”、“行為型”、“結構型”。本文會介紹兩種結構型設計模式。先看一下結構這個詞的定義:

“以一種確定方式構建的事物以及實體中各部分元素之間不同關係的彙總。”- www.merriam-webster.com/dictionary/…

結構型設計模式的主要作用是明確一段程式碼的功能,並說明如何使用。大部分的結構型設計模式可以通過編寫易讀介面,來實現對一段程式碼的簡化使用。因為一段程式碼勢必要與其它程式碼聯絡,如果要為程式碼段編寫出良好的介面,必須明確清晰地定義程式碼之間的各種關係。

外觀設計模式

“外觀可以定義為特殊結構化的建築物表面或者錯誤的、表面上的、人為的外形或效果”。- www.merriam-webster.com/dictionary/…

大部分情況下,可以使用外觀模式,為一組複雜介面建立一個簡單介面。或許你已經寫過“封裝”程式碼。“封裝”的意思就是對一段複雜程式碼的簡化使用。

外觀設計模式的示例 app

外觀設計模式示例的 playground 檔案,可以在 GitHub 找到。在這個例子裡展示了,如何通過外觀設計模式,來為沙盒檔案系統建立一個簡單的介面,供所有的 iOS app 使用。iOS 檔案系統是一個龐大的作業系統子系統,功能包括建立、讀取、刪除、移動、重新命名、拷貝檔案和目錄。允許獲取和設定檔案和目錄的後設資料,比如列出在指定目錄下的所有檔案。允許檢視檔案和目錄的狀態,比如某個指定檔案是否可寫。提供蘋果推薦、預定義的目錄名。實際上其包含的功能遠遠不止上面提到的這些。

由於 iOS 檔案系統是一個擁有如此多特性和功能的巨集大主題,因此也是一個非常好的例子,用來講解如何通過外觀設計模式來簡化程式碼的使用。外觀介面會廢棄掉無關功能和雜亂程式碼的部分。另一方面,外觀介面只會定義在某個具體 app 需要使用到的功能。或者在我的例子中,我將功能縮減到只有經常使用的那部分。這樣做的好處是保證程式碼在不同 app 中都是可複用、可擴充套件,可維護的。

基於面向協議程式設計和值語義,我將 iOS 檔案系統的主要特性進行了劃分,從而將其變成可複用、可擴充套件的單元:協議和協議擴充套件。

將四個協議組合成一個結構體,這個結構體代表了可以在所有 iOS 應用中使用的沙盒 iOS 目錄(還可以看 這篇文章)。因為未來你肯定會更多接觸到更多面向協議程式設計和值語義相關的主題,要注意術語 composedcomposition 在這裡屬於同義詞。

除此之外,為了讓你更專注於理解外觀設計模式的使用,在後面的程式碼中,我省略了 Swift 錯誤處理和通用錯誤檢查的程式碼。

外觀設計模式的示例程式碼

接下來就看看我的程式碼吧。先確保已經下載了我在 GitHub 上的 playground 檔案。下面是蘋果官方推薦的用於檔案系統操作的預定義目錄。

enum AppDirectories : String { 
case Documents = "Documents" case Inbox = "Inbox" case Library = "Library" case Temp = "tmp"
}複製程式碼

通過將檔案操作限定在上述目錄中,避免了複雜性,並遵循了人機介面指南的原則。

在探究檔案操作的核心程式碼之前,先來看看使用外觀設計模式所設計出來的介面吧。我建立了 iOSAppFileSystemDirectory 結構體,作為檔案系統常用功能的簡單可讀介面。這個介面適用於 AppDirectories 列舉下的所有目錄。事實上,我原本還可以加入諸如 符號化連結的建立,或者使用 FileHandle 類實現對檔案的精細控制。但是在實際情況中,我幾乎不太使用到這些功能,更重要的一點是,我想要保持程式碼的簡潔性。

我建立了由四個協議組成的外觀。(我知道你看到下面的程式碼中只遵循了三個協議,這其實是因為其中有一個協議繼承自另一個協議):

struct iOSAppFileSystemDirectory : AppFileManipulation, AppFileStatusChecking, AppFileSystemMetaData { 
let workingDirectory: AppDirectories init(using directory: AppDirectories) {
self.workingDirectory = directory
} func writeFile(containing text: String, withName name: String) ->
Bool {
return writeFile(containing: text, to: workingDirectory, withName: name)
} func readFile(withName name: String) ->
String {
return readFile(at: workingDirectory, withName: name)
} func deleteFile(withName name: String) ->
Bool {
return deleteFile(at: workingDirectory, withName: name)
} func showAttributes(forFile named: String) ->
Void {
let fullPath = buildFullPath(forFileName: named, inDirectory: workingDirectory) let fileAttributes = attributes(ofFile: fullPath) for attribute in fileAttributes {
print(attribute)
}
} func list() {
list(directory: getURL(for: workingDirectory))
}
} // 完成結構體 iOSAppFileSystemDirectory 的定義複製程式碼

下面是一些用於測試 iOSAppFileSystemDirectory 結構體的程式碼:

var iOSDocumentsDirectory = iOSAppFileSystemDirectory(using: .Documents)iOSDocumentsDirectory.writeFile(containing: "New file created.", withName: "myFile3.txt")iOSDocumentsDirectory.list()iOSDocumentsDirectory.readFile(withName: "myFile3.txt")iOSDocumentsDirectory.showAttributes(forFile: "myFile3.txt")iOSDocumentsDirectory.deleteFile(withName: "myFile3.txt")複製程式碼

接下來的程式碼是在執行了 playground 檔案中程式碼之後的控制檯輸出:

----------------------------LISTING: /var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Facade-Design-Pattern-1C4BD3E3-E23C-4991-A344-775D5585D1D7/DocumentsFile: "myFile3.txt"File: "Shared Playground Data"----------------------------File created with contents: New file created.(key: __C.FileAttributeKey(_rawValue: NSFileType), value: NSFileTypeRegular)(key: __C.FileAttributeKey(_rawValue: NSFilePosixPermissions), value: 420)(key: __C.FileAttributeKey(_rawValue: NSFileSystemNumber), value: 16777223)(key: __C.FileAttributeKey(_rawValue: NSFileExtendedAttributes), value: { 
"com.apple.quarantine" = <
30303836 3b356238 36656364 373b5377 69667420 46616361 64652044 65736967 6e205061 74746572 6e3b>
;

})(key: __C.FileAttributeKey(_rawValue: NSFileReferenceCount), value: 1)(key: __C.FileAttributeKey(_rawValue: NSFileSystemFileNumber), value: 24946094)(key: __C.FileAttributeKey(_rawValue: NSFileGroupOwnerAccountID), value: 20)(key: __C.FileAttributeKey(_rawValue: NSFileModificationDate), value: 2018-08-29 18:58:31 +0000)(key: __C.FileAttributeKey(_rawValue: NSFileCreationDate), value: 2018-08-29 18:58:31 +0000)(key: __C.FileAttributeKey(_rawValue: NSFileSize), value: 17)(key: __C.FileAttributeKey(_rawValue: NSFileExtensionHidden), value: 0)(key: __C.FileAttributeKey(_rawValue: NSFileOwnerAccountID), value: 502)File deleted.複製程式碼

我們來簡單討論下 iOSAppFileSystemDirectory 結構體所遵循的幾個協議。AppDirectoryNames 協議和擴充套件定義和實現了以 URL 型別獲取 AppDirectories 列舉中目錄完整路徑的方法。

protocol AppDirectoryNames { 
func documentsDirectoryURL() ->
URL func inboxDirectoryURL() ->
URL func libraryDirectoryURL() ->
URL func tempDirectoryURL() ->
URL func getURL(for directory: AppDirectories) ->
URL func buildFullPath(forFileName name: String, inDirectory directory: AppDirectories) ->
URL
} // end protocol AppDirectoryNamesextension AppDirectoryNames {
func documentsDirectoryURL() ->
URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
} func inboxDirectoryURL() ->
URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(AppDirectories.Inbox.rawValue) // "Inbox")
} func libraryDirectoryURL() ->
URL {
return FileManager.default.urls(for: FileManager.SearchPathDirectory.libraryDirectory, in: .userDomainMask).first!
} func tempDirectoryURL() ->
URL {
return FileManager.default.temporaryDirectory //urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(AppDirectories.Temp.rawValue) //"tmp")
} func getURL(for directory: AppDirectories) ->
URL {
switch directory {
case .Documents: return documentsDirectoryURL() case .Inbox: return inboxDirectoryURL() case .Library: return libraryDirectoryURL() case .Temp: return tempDirectoryURL()
}
} func buildFullPath(forFileName name: String, inDirectory directory: AppDirectories) ->
URL {
return getURL(for: directory).appendingPathComponent(name)
}
} // end extension AppDirectoryNames複製程式碼

AppFileStatusChecking 協議和擴充套件封裝了獲取檔案狀態資料的方法。這些檔案同樣儲存於 AppDirectories 列舉定義下的目錄。通過“狀態”,可以確定某個檔案是否存在,是否可讀等。

protocol AppFileStatusChecking { 
func isWritable(file at: URL) ->
Bool func isReadable(file at: URL) ->
Bool func exists(file at: URL) ->
Bool
}extension AppFileStatusChecking {
func isWritable(file at: URL) ->
Bool {
if FileManager.default.isWritableFile(atPath: at.path) {
print(at.path) return true
} else {
print(at.path) return false
}
} func isReadable(file at: URL) ->
Bool {
if FileManager.default.isReadableFile(atPath: at.path) {
print(at.path) return true
} else {
print(at.path) return false
}
} func exists(file at: URL) ->
Bool {
if FileManager.default.fileExists(atPath: at.path) {
return true
} else {
return false
}
}
} // end extension AppFileStatusChecking複製程式碼

AppFileSystemMetaData 協議和擴充套件實現了列出目錄內容和獲取擴充套件檔案的功能。 其目錄也是定義在 AppDirectories 列舉下。

protocol AppFileSystemMetaData { 
func list(directory at: URL) ->
Bool func attributes(ofFile atFullPath: URL) ->
[FileAttributeKey : Any]
}extension AppFileSystemMetaData{
func list(directory at: URL) ->
Bool {
let listing = try! FileManager.default.contentsOfDirectory(atPath: at.path) if listing.count >
0 {
print("\n----------------------------") print("LISTING: \(at.path)") print("") for file in listing {
print("File: \(file.debugDescription)")
} print("") print("----------------------------\n") return true
} else {
return false
}
} func attributes(ofFile atFullPath: URL) ->
[FileAttributeKey : Any] {
return try! FileManager.default.attributesOfItem(atPath: atFullPath.path)
}
} // end extension AppFileSystemMetaData複製程式碼

最後是 AppFileManipulation 協議和擴充套件,封裝了 AppDirectories 列舉目錄下的所有檔案操作方法,包括了讀、寫、刪除、重新命名、移動、拷貝修改副檔名等。

protocol AppFileManipulation : AppDirectoryNames { 
func writeFile(containing: String, to path: AppDirectories, withName name: String) ->
Bool func readFile(at path: AppDirectories, withName name: String) ->
String func deleteFile(at path: AppDirectories, withName name: String) ->
Bool func renameFile(at path: AppDirectories, with oldName: String, to newName: String) ->
Bool func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) ->
Bool func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) ->
Bool func changeFileExtension(withName name: String, inDirectory: AppDirectories, toNewExtension newExtension: String) ->
Bool
} extension AppFileManipulation {
func writeFile(containing: String, to path: AppDirectories, withName name: String) ->
Bool {
let filePath = getURL(for: path).path + "/" + name let rawData: Data? = containing.data(using: .utf8) return FileManager.default.createFile(atPath: filePath, contents: rawData, attributes: nil)
} func readFile(at path: AppDirectories, withName name: String) ->
String {
let filePath = getURL(for: path).path + "/" + name let fileContents = FileManager.default.contents(atPath: filePath) let fileContentsAsString = String(bytes: fileContents!, encoding: .utf8) print("File created with contents: \(fileContentsAsString!)\n") return fileContentsAsString!
} func deleteFile(at path: AppDirectories, withName name: String) ->
Bool {
let filePath = buildFullPath(forFileName: name, inDirectory: path) try! FileManager.default.removeItem(at: filePath) print("\nFile deleted.\n") return true
} func renameFile(at path: AppDirectories, with oldName: String, to newName: String) ->
Bool {
let oldPath = getURL(for: path).appendingPathComponent(oldName) let newPath = getURL(for: path).appendingPathComponent(newName) try! FileManager.default.moveItem(at: oldPath, to: newPath) // highlights the limitations of using return values return true
} func moveFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) ->
Bool {
let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory) let destinationURL = buildFullPath(forFileName: name, inDirectory: directory) // warning: constant 'success' inferred to have type '()', which may be unexpected // *let success =* try! FileManager.default.moveItem(at: originURL, to: destinationURL) return true
} func copyFile(withName name: String, inDirectory: AppDirectories, toDirectory directory: AppDirectories) ->
Bool {
let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory) let destinationURL = buildFullPath(forFileName: name, inDirectory: directory) try! FileManager.default.copyItem(at: originURL, to: destinationURL) return true
} func changeFileExtension(withName name: String, inDirectory: AppDirectories, toNewExtension newExtension: String) ->
Bool {
var newFileName = NSString(string:name) newFileName = newFileName.deletingPathExtension as NSString newFileName = (newFileName.appendingPathExtension(newExtension) as NSString?)! let finalFileName:String = String(newFileName) let originURL = buildFullPath(forFileName: name, inDirectory: inDirectory) let destinationURL = buildFullPath(forFileName: finalFileName, inDirectory: inDirectory) try! FileManager.default.moveItem(at: originURL, to: destinationURL) return true
}
} // end extension AppFileManipulation複製程式碼

介面卡設計模式

“適配”的含義是“通過修改讓一個事物更適合(用於新用途)。”- www.merriam-webster.com/dictionary/…

“介面卡”的含義是“用於適配不在初始使用意圖範圍內裝置的一種附加裝置。”- www.merriam-webster.com/dictionary/…

介面卡設計模式的作用是在不修改已有程式碼庫 “A” 的前提下,仍舊可以使用與程式碼庫 “A” 不相容的程式碼庫 “B”,並保證 “A” 可以正常工作。我們可以建立介面卡來保證 “A” 和 “B” 可以一起工作。其中一定要牢記的原則是程式碼庫 “A” 是不能被修改的。(這是因為修改會破壞原有程式碼或者我們根本就沒有這段原始碼)

介面卡設計模式示例 app

介面卡的 playground 檔案,可以在 GitHub 上找到。在這部分程式碼中,我們基於 iOS 檔案系統進行介面卡模式的討論,並基於 iOS 檔案系統設計了一個介面卡模式的例子。之前一章,我們已經實現了將 iOS 檔案系統中所有目錄和檔案的路徑表示為 URL 例項。想象一下下面的場景,在原有工程中已經存在了大量關於 iOS 檔案系統的程式碼,但是所有目錄和檔案的路徑都表示成了字串形式。那我們就必須要讓基於 URL 和基於 String 的程式碼可以協同工作。

介面卡設計模式的示例程式碼

接下來就看看程式碼吧。先確保已經下載了在 GitHub 上的 playground 檔案。為了在接下來的分析中更加專注於介面卡模式的討論,下面會使用簡化版本的 AppDirectories 列舉和 AppDirectoryNames 協議和擴充套件。

enum AppDirectories : String { 
case Documents = "Documents" case Temp = "tmp"
} protocol AppDirectoryNames {
func documentsDirectoryURL() ->
URL func tempDirectoryURL() ->
URL
} extension AppDirectoryNames {
func documentsDirectoryURL() ->
URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
} func tempDirectoryURL() ->
URL {
return FileManager.default.temporaryDirectory
}
}複製程式碼

一種方法是建立一個“專用”介面卡。這個介面卡會返回字串路徑,這些路徑全部歸屬於在 AppDirectories 下的目錄和檔案。

// 專用介面卡struct iOSFile : AppDirectoryNames { 
let fileName: URL var fullPathInDocuments: String {
return documentsDirectoryURL().appendingPathComponent(fileName.absoluteString).path
} var fullPathInTemporary: String {
return tempDirectoryURL().appendingPathComponent(fileName.absoluteString).path
} var documentsStringPath: String {
return documentsDirectoryURL().path
} var temporaryStringPath: String {
return tempDirectoryURL().path
} init(fileName: String) {
self.fileName = URL(string: fileName)!
}
}複製程式碼

下一部分是用於測試 iOSFile “專用”介面卡的程式碼,請注意程式碼中的註釋。

let iOSfile = iOSFile(fileName: "myFile.txt")iOSfile.fullPathInDocumentsiOSfile.documentsStringPath iOSfile.fullPathInTemporaryiOSfile.temporaryStringPath // 通過 `AppDirectoryNames` 協議,仍然能夠訪問到 URLiOSfile.documentsDirectoryURL()iOSfile.tempDirectoryURL()複製程式碼

最後是 playground 檔案中每一行程式碼對應的右側輸出,這些輸出代表了執行時每一行程式碼的值。參照上一段程式碼,我們可以進行逐行對照。

iOSFile"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents/myFile.txt""/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents" "/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp/myFile.txt""/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp" file:///var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-0A71F81A-9388-41F5-ACBE-52A1A61A9B99/Documents/file:///Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/F08EFF4F-8C4F-4BB7-B220-980E16344F18/tmp/複製程式碼

另外,我還傾向為字串型別的路徑設計一個介面卡協議。這樣就可以很方便地使用字串路徑來替代 URL 路徑。

// Protocol-oriented approachprotocol AppDirectoryAndFileStringPathNamesAdpater : AppDirectoryNames { 
var fileName: String {
get
} var workingDirectory: AppDirectories {
get
} func documentsDirectoryStringPath() ->
String func tempDirectoryStringPath() ->
String func fullPath() ->
String
} // end protocol AppDirectoryAndFileStringPathAdpaterNames extension AppDirectoryAndFileStringPathNamesAdpater {
func documentsDirectoryStringPath() ->
String {
return documentsDirectoryURL().path
} func tempDirectoryStringPath() ->
String {
return tempDirectoryURL().path
} func fullPath() ->
String {
switch workingDirectory {
case .Documents: return documentsDirectoryStringPath() + "/" + fileName case .Temp: return tempDirectoryStringPath() + "/" + fileName
}
}
} // end extension AppDirectoryAndFileStringPathNamesAdpater struct AppDirectoryAndFileStringPathNames : AppDirectoryAndFileStringPathNamesAdpater {
let fileName: String let workingDirectory: AppDirectories init(fileName: String, workingDirectory: AppDirectories) {
self.fileName = fileName self.workingDirectory = workingDirectory
}
} // end struct AppDirectoryAndFileStringPathNames複製程式碼

接下來是用於測試 AppDirectoryAndFileStringPathNames 結構體的程式碼。這個結構體遵守了 AppDirectoryAndFileStringPathNamesAdpater 介面卡協議。協議繼承自 AppDirectoryNames 協議。注意在程式碼中的兩段註釋。

let appFileDocumentsDirectoryPaths = AppDirectoryAndFileStringPathNames(fileName: "myFile.txt", workingDirectory: .Documents)appFileDocumentsDirectoryPaths.fullPath()appFileDocumentsDirectoryPaths.documentsDirectoryStringPath() // 通過 `AppDirectoryNames` 協議仍然可以訪問 URLappFileDocumentsDirectoryPaths.documentsDirectoryURL() let appFileTemporaryDirectoryPaths = AppDirectoryAndFileStringPathNames(fileName: "tempFile.txt", workingDirectory: .Temp)appFileTemporaryDirectoryPaths.fullPath()appFileTemporaryDirectoryPaths.tempDirectoryStringPath() // 通過 `AppDirectoryNames` 協議仍然可以訪問 URLappFileTemporaryDirectoryPaths.tempDirectoryURL()複製程式碼

最後是在 playground 檔案中右側的輸出。每一行代表了執行時的程式碼值,下面的輸出同樣和上一段程式碼是逐行對應的。

AppDirectoryAndFileStringPathNames"/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents/myFile.txt""/var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents" file:///var/folders/5_/kd8__nv1139__dq_3nfvsmhh0000gp/T/com.apple.dt.Xcode.pg/containers/com.apple.dt.playground.stub.iOS_Simulator.Swift-Adapter-Design-Pattern-A3DE7CC8-D60F-4448-869F-2A19556C62B2/Documents/ AppDirectoryAndFileStringPathNames"/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp/tempFile.txt""/Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp" file:///Users/softwaretesting/Library/Developer/XCPGDevices/52E1A81A-98AF-42DE-ADCF-E69AC8FA2791/data/Containers/Data/Application/CF3D4156-E773-4BC4-B117-E7BDEFA3F34C/tmp/複製程式碼

結論

設計模式不僅有利於程式碼複用,還能保證程式碼是不變、易讀、鬆耦合的,從而提高了可維護性和擴充性。當重複出現並且能加以抽象的功能在你的 app 中出現的時候,我希望你能應用一下設計模式,並 封裝進框架 中。這樣子你只需要寫一次程式碼,就可以一直複用啦。

再次感謝大家來 AppCoda 給我捧場。享受工作,堅持學習,下次再見吧!

本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 swift.gg

來源:https://juejin.im/post/5bff9a4cf265da61476ff015

相關文章