Swift:轻量级API的设计(二)

Swift的最强大功能之一就是在设计API方面给我们提供了极大的灵活性。这种灵活性不仅使我们能够定义易于理解和使用的函数和类型,还使我们能够创建给人以非常轻量级为第一印象的API,同时在需要的时候仍可以逐步暴露更多功能和复杂性。

本周,让我们看一下使这些轻量级API得以创建的一些核心语言功能,以及我们如何使用它们来通过组合的力量使功能或系统更加强大。


简书 - API

部分前置内容请先查看 Swift:轻量级API的设计(一)

可变参数和更进一步的组合

接下来,让我们看一下另一个非常有趣的语言功能——可变参数(variadic parameters)——以及它们可以解锁的API设计选择。

现在,我们说我们正在开发一个使用基于形状的绘图来创建其用户界面的一部分的应用程序,并且我们已经使用了与上述类似的基于结构的方法来建模每种形状的绘制方式到DrawingContext中:

struct Shape {
    var drawing: (inout DrawingContext) -> Void
}

上面我们使用inout关键字来启用值类型(DrawingContext)的传递,就好像它是引用一样。有关该关键字的更多信息以及一般的值语义,请查看在Swift中使用值语义

就像我们以前如何使用静态工厂方法轻松创建ImageTransform值一样,我们现在也能够将每个形状的绘图代码封装在完全独立的方法中,如下所示:

extension Shape {
    static func square(at point: Point, sideLength: Double) -> Self {
        Shape { context in
            let origin = point.movedBy(
                x: -sideLength / 2,
                y: -sideLength / 2
            )

            context.move(to: origin)
            context.drawLine(to: origin.movedBy(x: sideLength))
            context.drawLine(to: origin.movedBy(x: sideLength, y: sideLength))
            context.drawLine(to: origin.movedBy(y: sideLength))
            context.drawLine(to: origin)
        }
    }
}

由于将每个形状简单地建模为一个值,因此绘制它们的数组变得非常容易——我们要做的就是创建一个DrawingContext实例,然后将其传递到每个形状的闭包中以构建最终图像:

func draw(_ shapes: [Shape]) -> Image {
    var context = DrawingContext()
    
    shapes.forEach { shape in
        context.move(to: .zero)
        shape.drawing(&context)
    }
    
    return context.makeImage()
}

调用上面的函数看起来也很优雅,因为我们再次可以使用点语法来大大减少执行工作所需的语法量:

let image = draw([
    .circle(at: point, radius: 10),
    .square(at: point, sideLength: 5)
])

但是,让我们看看是否可以使用可变参数来更进一步。虽然不是Swift独有的功能,但结合Swift真正灵活的参数命名功能后,使用可变参数可以产生一些非常有趣的结果。

当参数被标记为可变参数时(通过在其类型中添加...后缀),我们基本上可以将任意数量的值传递给该参数——编译器会自动为我们将这些值组织到一个数组中,例如这个:

func draw(_ shapes: Shape...) -> Image {
    ...
    // 在我们的函数中,"shapes"仍然是一个数组:
    shapes.forEach { ... }
}

完成上述更改后,我们现在可以从对draw函数的调用中删除所有数组文字,而使它们看起来像这样:

let image = draw(.circle(at: point, radius: 10),
                 .square(at: point, sideLength: 5))
这看起来似乎不是很大的变化,但特别是在设计用更低级别的API来创建更多更高级别的API(例如我们的draw函数)时,使用可变参数可以使这类API感觉更加轻量级和方便。

但是,使用可变参数的一个缺点是,预先计算的值数组不能再作为单个参数传递。值得庆幸的是,在这种情况下,可以通过创建一个特殊的组(group)形状(就像draw函数本身一样),在一组基础形状上进行迭代并绘制它们来轻松解决:

extension Shape {
    static func group(_ shapes: [Shape]) -> Self {
        Shape { context in
            shapes.forEach { shape in
                context.move(to: .zero)
                shape.drawing(&context)
            }
        }
    }
}

完成上述操作后,我们现在可以再次轻松地将一组预先计算的Shape值传递给我们的draw函数,如下所示:

let shapes: [Shape] = loadShapes()
let image = draw(.group(shapes))

不过,真正酷的是,上述groupAPI不仅使我们能够构造形状数组,而且还使我们能够更轻松地将多个形状组合到更高级的组件中。例如,这是我们如何使用一组组合形状来表示整个图形(例如logo)的方法:

extension Shape {
    static func logo(withSize size: Size) -> Self {
        .group([
            .rectangle(at: size.centerPoint, size: size),
            .text("The Drawing Company", fittingInto: size),
            ...
        ])
    }
}

由于上述logo与其他方法一样都是Shape,因此只需调用一次draw方法就可以轻松绘制它,并使用与之前相同的优雅点语法:

let logo = draw(.logo(withSize: size))

有趣的是,尽管我们最初的目标可能是使我们的API更轻量级,但这样做也使它的可组合性灵活性也得到了提高。

结论 Conclusion

我们向“API designer’s toolbox”添加的工具越多,我们越有可能能够设计出在功能,灵活性和易用性之间达到适当平衡的API。

使API尽可能轻量级可能不是我们的最终目标,但是通过尽可能减少API的数量,我们也经常发现如何使它们变得更强大——通过使我们创建类型的方式更灵活,以及使他们组成。所有这些都可以帮助我们在简单性与功能之间实现完美的平衡

文章来自 John SundellLightweight API design in Swift,简单翻译了下半部分,上半部分Swift:轻量级API的设计(一)

赏我一个赞吧~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容