Swift的最強(qiáng)大功能之一就是在設(shè)計(jì)API方面給我們提供了極大的靈活性。這種靈活性不僅使我們能夠定義易于理解和使用的函數(shù)和類型,還使我們能夠創(chuàng)建給人以非常輕量級(jí)為第一印象的API,同時(shí)在需要的時(shí)候仍可以逐步暴露更多功能和復(fù)雜性。
本周,讓我們看一下使這些輕量級(jí)API得以創(chuàng)建的一些核心語言功能,以及我們?nèi)绾问褂盟鼈儊硗ㄟ^組合的力量使功能或系統(tǒng)更加強(qiáng)大。

Swift:輕量級(jí)API的設(shè)計(jì)(二)
功能和易用性的平衡
通常,當(dāng)我們?cè)O(shè)計(jì)各種類型和功能如何相互交互時(shí),我們必須在功能和易用性之間找到某種形式的平衡。使事情變得過于簡單,它們可能不夠靈活,無法使我們的功能不斷發(fā)展——但是,另一方面,過于復(fù)雜通常會(huì)導(dǎo)致沮喪,誤解并最終導(dǎo)致錯(cuò)誤。
舉例來說,假設(shè)我們正在開發(fā)一個(gè)應(yīng)用程序,該應(yīng)用程序使我們的用戶可以對(duì)圖像應(yīng)用各種濾鏡——例如,能夠從其相機(jī)膠卷或圖庫中編輯照片。每個(gè)濾鏡由一組圖像變換組成,并使用ImageFilter結(jié)構(gòu)定義,如下所示:
struct ImageFilter {
var name: String
var icon: Icon
var transforms: [ImageTransform]
}
關(guān)于ImageTransform API,當(dāng)前已將其建模為協(xié)議,然后由我們遵循并單獨(dú)實(shí)現(xiàn)各種類型的轉(zhuǎn)換操作:
typealias Image = UIImage
protocol ImageTransform {
func apply(to image: Image) throws -> Image
}
struct PortraitImageTransform: ImageTransform {
var zoomMultiplier: Double
func apply(to image: Image) throws -> Image {
return image
}
}
struct ContrastBoostImageTransform {
func apply(to image: Image) throws -> Image {
return image
}
}
struct GrayScaleImageTransform: ImageTransform {
var brightnessLevel: BrightnessLevel
func apply(to image: Image) throws -> Image {
return image
}
}
enum Icon {
case drama
}
enum BrightnessLevel {
case light
case dark
}
上述方法的一個(gè)核心優(yōu)勢是,由于每個(gè)變換都是作為自己的類型實(shí)現(xiàn)的,因此我們可以自由地讓每個(gè)類型定義自己的屬性和參數(shù)集——例如,如何使GrayScaleImageTransform接受BrightnessLevel來使圖片變成灰度。
然后,我們可以根據(jù)需要組合任意數(shù)量的上述類型,以形成每個(gè)濾鏡——例如,通過一系列轉(zhuǎn)換使圖像具有某種“戲劇性”外觀的濾鏡:
let dramaticFilter = ImageFilter(
name: "Dramatic",
icon: .drama,
transforms: [
PortraitImageTransform(zoomMultiplier: 2.1),
ContrastBoostImageTransform(),
GrayScaleImageTransform(brightnessLevel: .dark)
]
)
到目前為止,一切都很好。但是,如果我們仔細(xì)研究上述API,可以肯定地說,我們的選擇是為了提高功能和靈活性,而不是為了易于使用。由于每個(gè)轉(zhuǎn)換都是作為單獨(dú)的類型實(shí)現(xiàn)的,因此,由于沒有一個(gè)可以立即發(fā)現(xiàn)所有轉(zhuǎn)換的地方,因此使用者無法立即清楚我們的代碼庫包含哪種轉(zhuǎn)換。
與之相比,如果我們選擇使用枚舉代替協(xié)議,則將為我們提供所有可能選項(xiàng)的清晰概述:
enum ImageTransform {
case portrait(zoomMultiplier: Double)
case grayScale(BrightnessLevel)
case contrastBoost
}
使用枚舉還可以產(chǎn)生非常漂亮且可讀性強(qiáng)的調(diào)用,這使我們的API更加輕巧易用,因?yàn)槲覀兛梢允褂?strong>點(diǎn)語法dot-syntax來轉(zhuǎn)換所有的調(diào)用,如下所示:
let dramaticFilter = ImageFilter(
name: "Dramatic",
icon: .drama,
transforms: [
.portrait(zoomMultiplier: 2.1),
.contrastBoost,
.grayScale(.dark)
]
)
但是,盡管Swift枚舉在許多情況下都是一種出色的工具,但在此處它真的不是一個(gè)好的選擇。
由于每個(gè)轉(zhuǎn)換都需要執(zhí)行截然不同的圖像操作,因此在這種情況下使用枚舉將迫使我們編寫一個(gè)龐大的switch語句來處理這些操作中的每一項(xiàng)——這很可能會(huì)成為噩夢(mèng)。
Light as an enum, capable as a struct (這句怎么翻譯,輕如枚舉(enum),強(qiáng)如結(jié)構(gòu)體(struct)?)
值得慶幸的是,還有第三種選擇——可以讓我們兩全其美。與其使用協(xié)議(protocol)或枚舉(enum),不如使用結(jié)構(gòu)體(struct),而該struct又包含一個(gè)封裝了給定轉(zhuǎn)換各種操作的閉包:
struct ImageTransform {
let closure: (Image) throws -> Image
func apply(to image: Image) throws -> Image {
try closure(image)
}
}
請(qǐng)注意,不再需要
apply(to:)方法,但我們?nèi)栽谔砑釉摲椒ㄒ员3窒蚝蠹嫒菪?,并使調(diào)用的可讀性更好。
完成上述操作后,我們現(xiàn)在可以使用靜態(tài)工廠方法和屬性來創(chuàng)建我們的轉(zhuǎn)換——每個(gè)轉(zhuǎn)換仍可以單獨(dú)定義并具有自己的一組參數(shù):
extension ImageTransform {
static var contrastBoost: Self {
ImageTransform { image in
image
}
}
static func portrait(withZoomMultipler multiplier: Double) -> Self {
ImageTransform { image in
image
}
}
static func grayScale(withBrightness brightness: BrightnessLevel) -> Self {
ImageTransform { image in
image
}
}
}
現(xiàn)在,函數(shù)、閉包單表達(dá)式函數(shù)將會(huì)隱式返回??梢詫?code>Self用作靜態(tài)工廠方法的返回類型,Swift 5.1中的Self關(guān)鍵字 。
上面方法的優(yōu)點(diǎn)在于,我們回到了將ImageTransform定義為協(xié)議時(shí)所具有的靈活性和強(qiáng)大功能,同時(shí)仍然能夠使用與使用枚舉時(shí)大致相同的點(diǎn)語法:
let dramaticFilter = ImageFilter(
name: "Dramatic",
icon: .drama,
transforms: [
.portrait(withZoomMultipler: 2.1),
.contrastBoost,
.grayScale(withBrightness: .dark)
]
)
點(diǎn)語法與枚舉無關(guān),而是可以代替任何類型的靜態(tài)API,它的功能非常強(qiáng)大——甚至可以通過將上述過濾器創(chuàng)建建模為計(jì)算的靜態(tài)屬性,使我們進(jìn)一步封裝東西好:
extension ImageFilter {
static var dramatic: Self {
ImageFilter(
name: "Dramatic",
icon: .drama,
transforms: [
.portrait(withZoomMultipler: 2.1),
.contrastBoost,
.grayScale(withBrightness: .dark)
]
)
}
}
經(jīng)過以上所有操作的結(jié)果是,我們現(xiàn)在可以執(zhí)行一系列非常復(fù)雜的任務(wù)——應(yīng)用圖像過濾器和轉(zhuǎn)換——并將它們封裝到一個(gè)API中,從表面上看,它像將值傳遞給函數(shù)一樣輕巧:
let filtered = image.withFilter(.dramatic)
盡管可以輕松地將上述更改視為僅添加“語法糖(syntactic sugar)”,但我們不僅改善了API讀取的方式,還改善了其組成的方式。由于所有的轉(zhuǎn)換和過濾器現(xiàn)在都只是值,因此可以將它們以多種方式組合在一起——不僅使它們更輕巧,而且也更加靈活。
文章來自 John Sundell的Lightweight API design in Swift,簡單翻譯了上半部分,剩下的部分Swift:輕量級(jí)API的設(shè)計(jì)(二)
注:文中部分代碼有做補(bǔ)充和修改,使得可以正常編譯
附:
withFilter()方法實(shí)現(xiàn)
extension Image {
func withFilter(_ imageFilter: ImageFilter) -> Image? {
var image = self
for trans in imageFilter.transforms {
do {
try image = trans.closure(self)
return image
} catch let error {
print(error)
return nil
}
}
return image
}
func withTransform(_ imageTransform: ImageTransform) -> Image? {
return try? imageTransform.closure(self)
}
}
補(bǔ)充了withTransform()方法,提供一個(gè)可以自由組合ImageTransform的方法,也提供一種API開發(fā)思路,可以提供預(yù)置的組合方法,也提供完全自定義的方法,使用示例:
let image = UIImage(named: "filter")!
//1、文中出現(xiàn)的使用預(yù)置濾鏡方法
let filtered = image.withFilter(dramaticFilter)
//2、自定義濾鏡方法一
let dramaticFilter = ImageFilter(
name: "Dramatic",
icon: .drama,
transforms: [
.portrait(withZoomMultiplier: 2.1),
.contrastBoost,
.grayScale(withBrightness: .dark),
]
)
let filtered = image.withFilter(dramaticFilter)
//2、自定義濾鏡方法二
let filtered = image
.withTransform(.portrait(withZoomMultiplier: 2.1))?
.withTransform(.contrastBoost)?
.withTransform(.grayScale(withBrightness: .dark))
賞我一個(gè)贊吧~~~