Swift:輕量級(jí)API的設(shè)計(jì)(一)

Swift的最強(qiáng)大功能之一就是在設(shè)計(jì)API方面給我們提供了極大的靈活性。這種靈活性不僅使我們能夠定義易于理解和使用的函數(shù)和類型,還使我們能夠創(chuàng)建給人以非常輕量級(jí)為第一印象的API,同時(shí)在需要的時(shí)候仍可以逐步暴露更多功能和復(fù)雜性。

本周,讓我們看一下使這些輕量級(jí)API得以創(chuàng)建的一些核心語言功能,以及我們?nèi)绾问褂盟鼈儊硗ㄟ^組合的力量使功能或系統(tǒng)更加強(qiáng)大。

簡書 - API

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 SundellLightweight 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è)贊吧~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容