做者:Andrew Jaffee,原文连接,原文日期:2018-09-04 译者:郑一一;校对:BigNerdCoding,pmst,Forelax;定稿:Forelaxios
本文是个人设计模式系列教程的第三篇。在第一篇文章中,我介绍了 建立型模式中的工厂模式和单例模式。在第二篇文章中,又讨论了一下 行为型模式中的观察者模式和备忘录模式。git
在本文中,我会就结构型模式中的外观模式和适配器模式分别举一个例子。首先,我建议你先去阅读前面提到的两篇文章,这会有助于你更熟悉软件设计模式的一些概念。除了简短地介绍一下设计模式的组成,我不会再重复介绍全部关于设计模式的概念了。若是须要了解,均可以在前面写的 第一篇、 第二篇 中找到。github
接下来的几节,咱们先来简单回顾一下设计模式的通用概念。“Gang of Four” (“GoF”) Erich Gamma,Richard Helm,Ralph Johonson,和 John Vlissides 在他们“设计模式:面向对象软件设计复用的基本原理”的重要著做里整理了 23 种经典的设计模式。今天咱们重点关注的是两种结构型设计模式:外观模式和适配器模式。web
你可能会发现世面上很是多设计模式教程的示例代码,仍然是基于面向对象编程原则(OOP)、引用语义和 引用类型(classes)编写的。因此,我决定编写一套基于 面向协议编程原则(POP)、值语义和 值类型(structs)的设计模式系列教程。若是你已经看过了我以前写的两篇文章,我但愿你还可以熟悉一下 OOP 和 POP,引用语义和值语义这些概念。若是你还不是特别熟悉,我强烈建议赶忙去了解一下这些主题。本文所举的例子是所有基于 POP 和值语义的。编程
设计模式是开发者用于管理软件复杂性极其重要的工具。做为常见的模板技术,它很好地对软件中相似的、重复出现的、容易识别的问题进行了概念化抽象。咱们能够将它视做最佳实践,从而应用到平常中会遇到的那些编程场景中。举一个具体的例子,回想一下你在日常写代码过程当中有多少次会使用或写了遵照 观察者设计模式 的代码吧。swift
在观察者模式中,被观察者(通常来讲是一个关键资源)会给全部依赖于本身的观察者,广播通知其内部状态的变化。观察者必须告知被观察者本身想接收通知,换句话说,观察者必须订阅通知。用户受权的 iOS 弹窗推送通知,就是一个典型的观察者模式的例子。设计模式
GoF 将 23 种设计模式概括为三种类型,分别是“建立型”、“行为型”、“结构型”。本文会介绍两种结构型设计模式。先看一下结构这个词的定义:xcode
“以一种肯定方式构建的事物以及实体中各部分元素之间不一样关系的汇总。” - www.merriam-webster.com/dictionary/…bash
结构型设计模式的主要做用是明确一段代码的功能,并说明如何使用。大部分的结构型设计模式能够经过编写易读接口,来实现对一段代码的简化使用。由于一段代码势必要与其它代码联系,若是要为代码段编写出良好的接口,必须明确清晰地定义代码之间的各类关系。app
“外观能够定义为特殊结构化的建筑物表面或者错误的、表面上的、人为的外形或效果”。 - www.merriam-webster.com/dictionary/…
大部分状况下,可使用外观模式,为一组复杂接口建立一个简单接口。或许你已经写过“封装”代码。“封装”的意思就是对一段复杂代码的简化使用。
外观设计模式示例的 playground 文件,能够在 GitHub 找到。在这个例子里展现了,如何经过外观设计模式,来为沙盒文件系统建立一个简单的接口,供全部的 iOS app 使用。iOS 文件系统是一个庞大的操做系统子系统,功能包括建立、读取、删除、移动、重命名、拷贝文件和目录。容许获取和设置文件和目录的元数据,好比列出在指定目录下的全部文件。容许查看文件和目录的状态,好比某个指定文件是否可写。提供苹果推荐、预约义的目录名。实际上其包含的功能远远不止上面提到的这些。
因为 iOS 文件系统是一个拥有如此多特性和功能的宏大主题,所以也是一个很是好的例子,用来说解如何经过外观设计模式来简化代码的使用。外观接口会废弃掉无关功能和杂乱代码的部分。另外一方面,外观接口只会定义在某个具体 app 须要使用到的功能。或者在个人例子中,我将功能缩减到只有常用的那部分。这样作的好处是保证代码在不一样 app 中都是可复用、可扩展,可维护的。
基于面向协议编程和值语义,我将 iOS 文件系统的主要特性进行了划分,从而将其变成可复用、可扩展的单元:协议和协议扩展。
我将四个协议组合成一个结构体,这个结构体表明了能够在全部 iOS 应用中使用的沙盒 iOS 目录(还能够看 这篇文章)。由于将来你确定会更多接触到更多面向协议编程和值语义相关的主题,要注意术语 composed 和 composition 在这里属于同义词。
除此以外,为了让你更专一于理解外观设计模式的使用,在后面的代码中,我省略了 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/Documents
File: "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 AppDirectoryNames
extension 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" 是不能被修改的。(这是由于修改会破坏原有代码或者咱们根本就没有这段源代码)
适配器的 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.fullPathInDocuments
iOSfile.documentsStringPath
iOSfile.fullPathInTemporary
iOSfile.temporaryStringPath
// 经过 `AppDirectoryNames` 协议,仍然可以访问到 URL
iOSfile.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 approach
protocol 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` 协议仍然能够访问 URL
appFileDocumentsDirectoryPaths.documentsDirectoryURL()
let appFileTemporaryDirectoryPaths = AppDirectoryAndFileStringPathNames(fileName: "tempFile.txt", workingDirectory: .Temp)
appFileTemporaryDirectoryPaths.fullPath()
appFileTemporaryDirectoryPaths.tempDirectoryStringPath()
// 经过 `AppDirectoryNames` 协议仍然能够访问 URL
appFileTemporaryDirectoryPaths.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。