美文网首页
(译) Swift 中的面向协议编程

(译) Swift 中的面向协议编程

作者: mountainKingG | 来源:发表于2020-05-16 22:43 被阅读0次

    大家好 我叫戴夫·亞伯拉罕 我是Swift标准库的技术主管 今天站在这里同你们一起是我的荣幸 很高兴看到房间里的你们

    接下来的40分钟请 丢掉你们通常思考编程的方式

    我们接下来要一起做的事情不一定容易 但我向你们保证如果你们跟着 我的思路这一定是值得的

    我现在来和你们谈一谈 Swift设计核心的主题 并介绍一种编程方式 这种编程方式有可能改变一切

    但首先请允许我先向你们 介绍我的一位朋友

    他是 Crusty

    现在可能你们每个人 都正在使用这个家伙的某一个版本 Crusty是个老式的程序员 他不相信调试器不使用集成开发环境 不 他喜欢 80x24的终端窗口 和纯文本 非常感谢

    他对最近的编程时尚不以为然

    现在我已经学会期待 Crusty 有点愤世嫉俗脾气古怪 但是即便如此有时仍会令我惊讶 比如上个月我们正在讨论应用开发 他坦率地说“我不做面向对象”

    我不敢相信我的耳朵 我的意思是面向对象程序设计 大约起源于20世纪70年代 所以它并不能算是新奇的编程时尚 此外我们一起构建的 很多神奇的东西 我 你 还有那些 我们所站在其肩膀上的工程师们 都由对象构建而成

    “来吧”我边走向他的老式黑板边说 “面向对象程序设计棒极了 看看你可以使用类做些什么”

    是的

    所以首先你可以把 相关数据和操作进行分组 然后就可以建造墙壁将我们的代码内外 分开这样就可以使我们保持不变量

    然后我们使用类来 表达相关想法如窗口或通信信道

    它们给我们一个命名空间用于帮助 避免随着软件扩展所带来的冲突

    它们有惊人的表达语法 所以我们可以写方法调用和属性 并把它们链接起来 我们可以做下标

    我们甚至可以做属性用于计算

    最后类的扩展性是开放的 所以如果类作者遗漏了 一些我需要用到的东西 我可以继续向前并在后续将其添加上 此外这些事物一起

    这些事物使我们能够管理复杂的事物 而这正是程序设计中最主要的挑战

    这些属性它们直接解决了 我们正在努力解决的软件开发中的问题

    在这一点上我自己颇受启发 然而 Crusty 只是哼了一声叹了口气

    他让我十分泄气

    如果这还不够糟糕的话 片刻之后他完成了这个句子

    因为这是真的在Swift 任何一种你可以说出的 类型都是一等公民 它能够利用所有这些功能

    所以我后退了一步并试图 找出使我们完成的所有事情运行的 核心功能在面向对象程序设计中

    很显然它一定是来自于只能用 类来处理的某些事物 比如继承

    这让我特别考虑 这些结构是如何做到代码共享 及细密定制的

    因此,举个例子一个超类可以定义 一个实质的具有复杂逻辑的方法 而子类无偿得到超类已完成的所有工作

    它们只是继承了这些内容

    但真正不可思议的事 发生在当超类的作者 把这个操作的一小部分断为 独立的定制点

    这些定制点可被子类覆盖

    且这种定制被覆盖在继承的实现上

    这使得困难的逻辑 在具备开放的灵活性 和特殊变化的同时可被重新使用 现在我确信我可以说服他 “哈”我对 Crusty 说 “很显然现在你不得不 臣服于类的力量了”

    “再坚持该死的一分钟”他回答说 “首先我用你一直说的结构做定制 其次 对 类是很厉害 但让我们来谈一谈用类所需要的代价

    我已经有三个重要的 关于类的牢骚”Crusty 说

    然后他从列表上开始了抱怨

    “首先你有了你的自动共享”

    现在你们都知道这是什么样子的

    A传递给B一些看起来完全清晰的数据

    然后B想“好 会话结束”

    但现在我们有一种情形 A和B都有他们自己非常合理的世界观 而这恰好是错误的

    因为现实是这样的 最终A厌恶了 严肃的数据取而代之喜欢小马 谁不喜欢一匹好的小马?

    一切都很好直到B之后发现了这些数据 很久之后她从A那里得到 且已有一个令人吃惊的变化

    B 想要的是她的数据 而不是 A 的小马

    好 Crusty 激昂地讲着 这是如何结束的

    “首先”他说“你开始疯狂地复制一切 镇压你代码中的错误 但现在你现在做了太多的副本 而这拖慢了代码的运行速度 然后有一天你正在处理 某个调度队列中的事情 突然你进入了一种紊乱状态下 因为线程正在共享可变状态 所以你开始添加锁以保护不变量 但是这些锁更加拖延了代码的运行速度 甚至可能导致死锁 所有的这些都在增加复杂度 其结果可总结为一个词 故障”

    不过这些对于Cocoa的 程序员来说都不是新闻

    这不是新闻这些年我们已经 在应用一种语言特征的组合 像@property(copy)和编码约定 来解决这个问题

    而我们仍然被咬 举个例子 在Cocoa文件中有这样一个警告 关于修改一个可变集合 在你通过它进行迭代时

    对吧 所有这些都是由 从类中继承的可变状态的 隐式共享导致的

    但这并不适用于Swift

    为什么不适用呢? 这是因为Swift集合都是数值类型 所以你正在迭代的这些 和你正在修改的这些是截然不同的

    好 Crusty 列表上的第二条

    类继承太具有侵入性

    首先 它太庞大你有且仅有一个超类

    那么假使你需要塑造 多个抽象将会怎样? 可以是一个集合并序列化吗?

    当然 除非集合和序列化是类

    因为类继承是单继承 类变得臃肿随着所有 相关的事情被放在一起 你也不得不选择超类在你定义类时 而非在后续某些扩展时

    其次如果超类存储了属性

    你必须接受它们

    并且别无选择

    然后因为它存储了属性 你必须将其初始化

    然后就像 Crusty 所说 “指定便利是必须的 噢天哪”

    因此你又必须确信你理解如何 与超类相互作用而不破坏它的常量 对吗 最后对于类作者来说这很自然 写代码就好像他们知道他们的方法

    将会做些什么 不用对 方法可能被覆盖负责任

    因此经常有至关紧要却有不成文的约定 关于哪些实际是允许 覆盖的以及比如你是否需要 链接到超类方法 如果你要链接到超类方法 那么位置是在方法的开头还是在结尾 抑或是在中间的某处呢

    所以,再一次对于Cocoa的 程序设计员来说仍然不是新闻 对吗 这就是为什么我们到处使用 委托模式在Cocoa

    好 Crusty 列表上的最后一条

    类被证明完全不适合于 类型关系很重要的问题

    所以如果你曾经尝试过使用类 来表达一个对称操作 比如比较你知道我的意思

    举个例子假设你打算写通用分类 或二进位检索类似这样 你需要一种方法来比较两个元素 使用类你最终得到像这样的一些结果

    当然你不能用这种 方法只写Ordered 因为Swift会预先要求方法体

    那么我们可以放些什么呢?

    请记得我们对于Ordered 的任意示例还一无所知

    所以如果这个方法没有由子类实现

    那么除了陷阱我们什么都做不了

    现在这是表明我们可以 对抗类型系统的第一个迹象

    如果我们没有认识到这一点 这也是我们欺骗自己的开始

    因为我们对这个问题置之不理告诉自己 只要每个Ordered子类 预先执行一切都会好的

    对吗?让它成为子类的问题

    如此我们继续进行 执行Ordered的一个例子

    所以这就是子类 它得到一个双精度类型值 我们覆盖优先来做比较

    当然除去它不起作用的情况

    看“其它”只是某些任意的 Ordered 不是一个数字因此我们不知道 “其他”有数值属性 事实上它可能是个标签 具有文本属性

    所以现在我们需要向 下转型以得到正确的类型

    但是等一下假设“其它” 结果是一个标签?

    现在我们将要陷阱

    对吗 所以这听起来很像 我们在预先写方法体时 遇到的问题在超类中

    并且我们现在没有比之前更好的答案

    这是静态类型安全漏洞

    它为什么会发生? 这是因为类不让我们表达 这种至关紧要的类型关系 自身的类型和其他的类型之间的关系

    事实上你可以把这个用作 “代码异味”

    所以任何时候你在 代码中看到强制的向下转型 这很好地象征了有些重要的 类型关系已经丢失 而这通常是由于抽象类的使用所造成的

    好 显然我们需要的是更好的抽象机制

    不强制我们接受隐式共享
    不丢失类型关系
    不强制我们只选择一种抽象且 在定义类型时进行选择
    不强制我们接受不想要的实例数据 以及相关的初始化复杂度
    最后不会留下模棱两可 关于需要覆盖什么

    当然我正在讲述的便是协议

    协议有所有这些优势这就是为什么 我们创造Swift时也就是我们创造 第一个面向协议的程序设计语言

    是的 Swift是很棒的 面向对象程序设计 但从循环和字符串的工作方式 到泛型标准库的重点 其核心Swift是面向协议的

    希望到你离开时 你可以更加面向协议

    所以为使你迈出右脚开始 我们在Swift有一种说法 不要从类开始 而要从协议开始

    那么让我们来做一下上一个例子

    好 首先我们需要一个协议 Swift马上会说 我们不可以在这儿放方法体

    而这实际上是很好的因为这意味着 我们要以动态运行时间检查换 静态检查

    对 在完成时预先执行

    好 接下来它会说我们 没有覆盖任何东西 当然我们没有 我们不再有基类 对吗 没有超类 没有覆盖

    我们可能根本不希望数字首先是类 因为我们希望它像数字一样操作 对吗 那么让我们马上来做 两件事情把这做成一个结构

    好 我想在这儿暂停一下 欣赏一下我们现在来到何处 因为这又是完全有效的代码了

    好 协议正完全扮演着相同的角色 就像在我们这个例子的 第一个版本中类的角色一样 这绝对更好 我的意思是我们不再有那致命的错误

    但是我们没有定位潜在的 静态类型安全漏洞 因为我们仍然需要强制的向下转型 因为“其他”仍然是 某些任意的Ordered

    那么让我们用数字替代丢掉类型转换

    现在Swift要说签名不匹配

    为解决这个问题 我们需要在协议签名 中用Self来替代Ordered

    我们称之为Self要求

    因此当你在协议中看到Self 它是类型的占位符用来符合 那个协议模型类型

    如此现在我们又有了有效的代码了

    现在让我们看一下如何使用协议

    所以这是二进位检索

    它也能够完美运行在我们向 Ordered加入Self要求之前

    这里的Ordered数组是一个声明

    声明我们要处理 Ordered类型的异构数组 因此这个数组可以混合包含数字 与标签 对吧

    由于我们对 Ordered做了这个改变 向其添加了Self需求编译器将 强制将其变为同构的 像这样

    这个人说“我在使用任意一个 Ordered类型T的同构数组”

    现在你可能会想强制使数组 成为同构数组这太限制了 或者有些失去了泛函性或灵活性或其他 但是如果你想一下 原始的签名真的是个谎言 我们从没真正处理过异构情况 除了通过陷阱的方式 对吧 同构数组正是我们要的

    那么一旦你在协议中添加Self要求 这将使协议进入一个非常不同的世界 在这里功能中大大减少了类的重复

    它不再作为类型使用

    集合从异构变为同构

    实例之间的相互作用不再意味着 所有模型类型之间的相互作用

    我们用静多态来替换动多态 但是作为我们传递给 编译器的额外的类型信息的回报 这样更具可优化性

    所以 两个世界

    在后续的演讲中我将向你们演示如何 在两者之间搭桥至少一种方式

    我理解了协议静态方面的工作原理 但我还不确定是否 要相信Crusty 协议真的可以取代类 所以我给他设了一个挑战 构建我们通常使用面向 对象程序设计的事物但是要用协议

    我脑海中浮现一个小的图表应用 你可以在绘图界面拖拽落成形状 然后可以与它们交互

    所以我让 Crusty 构建文档并显示模型

    这是他想出来的

    首先他构建了一些基本绘图 现在你可以想象 Crusty并不是在做图形用户界面 相较而言他更倾向文本

    他的基本绘图只是 输出了你提出的绘图命令 对吗? 我不情愿地承认了这很可能足以 证明他的观点 然后他新建了Drawable协议 为我们的所有绘图元素提供通用接口 好 这很简单了

    然后他开始创建形状比如多边形

    现在这里需要注意的第一件事 关于多边形这是一个数值类型 由其他数值类型创建而成 这是一个包含多点数组的结构

    为了画一个多边形 我们来到最后一个拐角 然后我们在所有交角处重复循环 画线

    好 下面是圆

    同样的圆也是一个数值类型 由其他数值类型创建而成 它是包含中心和半径的结构

    现在为了画一个圆 我们从0到2π弧度拽出一个弧形

    那么现在我们就可以 通过圆和多边形来创建图表了

    “好的” Crusty 说 “我们来将她做个旋转”

    他这么做了

    接下来就是图表

    图表就是一个Drawable类 这是另一个数值类型

    它为什么是数值类型呢?这是因为所有 Drawable类都是数值类型 因此Drawable类的 数组也是数值类型 让我们回到之前的话题

    好 这里

    因此由于这是我的图表中唯一的事物 这个图表也是数值类型

    因此要绘制它我们只需要遍历所有 元素并画下来每一个元素

    好 现在来作一个旋转

    那么 我们要测试一下

    所以Crusty新建了一个圆 以非常特别的中心和半径 然后以神秘的Spock一般的精度 他添加了一个三角形

    最后他在其周围创建了 一个图表并让它绘制

    “瞧”Crusty 耀武扬威地说 “就像你能明白地看到的 这是一个带圆的等边三角形内切于圆”

    也许我不擅长在脑海中做三角学问题 不像 Crusty 那样 但是“不 Crusty ”我说 “我不能很明白地看到这些 我会觉得这个延时更加有趣 如果我做一些实际有用的 比如给我们的应用 画到屏幕上”

    我从烦恼中恢复过来之后 我决定使用 CoreGraphics重写渲染器

    我告诉他我将要做这些 他说“再等一会儿猴小子

    如果你这样做了 我还怎样测试我代码呢?”

    然后他展现了一个很有说服力的案例 在测试中使用纯文本 如果我们正在做的 事情中发生了某些改变 我们可以立马在输出中看到

    然而他提出我们做一点 面向协议的程序设计

    然后他复制了他的 渲染器然后将其写入协议中

    然后你必须删除主体 好 这就是了 然后他重命名了 原始的渲染器并将它改为一致

    现在 所有的代码重构使得 我变得不耐烦 因为我很想在屏幕上看到这些东西

    我想抢过来实现 CoreGraphics的渲染器 但我必须等到 Crusty 再次测试他的代码

    等到他终于满意了他说“好了

    你打算在渲染器中放些什么呢?”

    我说“一个CGContext CGContext 基本有渲染器需要的一切”

    实际上在纯C语言接口范围内 它基本就是一个渲染器

    “好” Crusty 说 “把键盘给我” 他从我这儿夺去某样东西然后 极快地做了某些事情 太快以至于我都没有看到做了些什么

    “等一下”我说

    “你刚刚只是把每个 CGContext写入渲染器吗?”

    他...我是说它什么也没做 但是这有些令人吃惊 我甚至不需要添加一种新的类型

    “你还在等什么?Crusty说 “在那些大括号中填入内容”

    所以我把必要的 CoreGraphics粘着都倒了进去 把所有都扔进了游戏场 这就是了

    现在你可以下载这个游戏场 它演示了我在这儿演讲的所有内容 在我们结束之后

    不过还是回到我们的例子

    为了干扰我Crusty之后做了这些

    现在我需要花一点时间了解为什么绘制 在这个点上没有进入无线循环

    如果你想了解更多的话 你可以听一下周五的讲习会

    但这也并没有改变显示

    最终Crusty决定向我 演示正在发生的事情 在他的纯文本输出中 然后事实证明它只 重复了相同的绘制命令

    两次 所以作为一个更面向图形的家伙 我很想看到结果 所以我创建了一个小 比例的适配器并用它包裹住图表 这就是结果 你可以在游戏场看到这些所以我不打算 在这儿深究小比例适配器

    不过这也是一种

    使用协议的演示我们可以做到所有 与使用类可以做到的相同的事情 适配器通常设计模式

    好 现在我想反思一下 使用TestRenderer 都做了些什么 因为它事实上是有些杰出的

    通过从特定的渲染器中解耦文件模型 他能够插入检测组件 以显示我们正在做的一切 我们的代码正在做的一切详细地

    后来我们就在我们的 代码中应用了这种方法

    我们发现我们使用协议解耦的事物越多 所有的事物的可测性就越强

    这种测试与 使用mock测试得到的结果很像 但这样做更好

    mock测试本质上是脆弱的

    你需要结合测试中的代码 测试代码的实现细节

    正因这种脆弱性它们无法与 Swift 强大的静态类型 系统很好地融合

    协议给我们提供了有原则的接口 以供使用这由语言进行实施 但仍会给我们hook以插入所有 我们所需要的检测设备

    好 回到我们的例子 因为我们现在需要认真地 讨论一下Bubbles

    好我们希望这个图表 应用受孩子们的欢迎 当然孩子们喜欢 Bubbles(气泡) 因此在图表中Bubbles 只是一个内部圆圈偏移量 围绕外围圆圈中心

    以此来表现加亮区 所以你有两个圆 就像这样

    当我把这段代码放到上下文 Crusty 开始变得很激动

    所有的代码副本都使他抓狂 如果 Crusty 不高兴 那么没有人可以高兴

    “瞧 他们都是完整的圆”他喊道 “我只想写这段”

    我说“冷静Crusty冷静 我们可以这么做

    我们需要做的就是 再添加一个协议的要求

    然后当然我们更新模型来供应它 我们有测试渲染器

    然后还有 CGContext”

    现在这个点上 Crusty 脱掉了鞋子 拿它敲着桌子因为这里 我们又一次在重复代码

    他从我这里把键盘夺了回来抱怨着 说所有的事情都需要他自己完成

    他开始教我使用 Swift的一个新特征

    这就是协议扩展

    据说“渲染器的所有模型 都有circleAt的这个实现”

    现在我们有一种实现 渲染器的所有模型都在共享这种实现 注意到我们仍有这个 circleAt 要求在那里 你可能会问“有要求意味着什么? 这个要求也可以在扩展中立即实现”

    很好的问题 答案是协议要求新建定制点

    为了见证这是如何表现的 让我们先推翻这种方法体 而在扩展中添加另一种方法 添加一种不被要求支持的方法

    现在我们可以扩展Crusty的 来实现这两个方法

    然后我们只需要调用它们

    好 现在发生的事情完全不会令人吃惊 我们直接调用 TestRender中的实现 而协议并没有参与其中 对吧? 如果我们删除这种一致性 我们将得到相同的结果

    不过现在我们修改下上下文 这样Swift就只知道它是一个渲染器 而不是 TestRenderer

    来看一下发生了什么

    所以因为circleAt是要求 我们的模型获得了定制它的特权 然后定制被调用

    那个

    但是rectangleAt不是要求 所以TestRenderer中的实现 只覆盖到协议和上下文 你只知道有渲染器而非 TestRenderer的时候 协议实现被调用

    这有点奇怪 不是吗?

    那么 这是否意味着 rectangleAt应该是要求?

    也许在这种情况下它应该 是因为有些渲染器非常有可能 有更有效的方式来画矩形 与坐标系相配合

    但是 协议扩展中的所有事物是否也 是由要求支持的呢? 不一定

    我是说有些应用程序 界面并不打算作为定制点 所以有时正确的解决方法是 只覆盖到模型中的要求 而不要覆盖到模型中的方法

    那么这种新特征偶然地变革了 我们在Swift标准库上的工作

    有时我们使用协议扩展所做的事情

    感觉很神奇

    我真心希望你们可以 享受使用最新的库进行工作 就像我们享受应用这些到库 以及更新库一样 我想先撇开我们的故事 这样我就可以向你们展示我们使用 协议扩展在标准库上做的一些事情 还有一些其他的技巧

    首先来讲一下新的indexOf方法

    这个方法遍历集合的指针 直到它找到与我们 正在查找的相等的元素 然后返回这个指针 如果它没有找到结果那么返回空 非常简单对吧?

    但是如果我们这样写 就会有一个问题

    一个任意集合的元素 不能对等地比较

    因此为了解决这个问题 我们可以约束扩展 这是这个新特征的另一方面

    所以这么说扩展应用于 集合元素类型是Equatable时

    我们已经给Swift传递了它所 需要的信息以允许等式比较

    现在我们已经看到了 约束扩展的一个简单示例 让我们重温一下二进位检索

    我们将其应用到整型数组上

    好 整型不符合Ordered类

    这是一种简单的解决办法 我们只要添加一致性

    好 那对于字符串又会怎样呢?

    当然 这对字符串并没有用 所以我们再做一次 现在在Crusty开始敲桌子之前 我们很想分析出其中原因

    小于运算符出现在 Comparable协议中 所以我们可以在 Comparable协议中操作

    像这样

    现在我们正在预先提供那些一致性 所以一方面这是很好的 如果我想对双精度数据 进行二进位搜索 我所需要做的就是添加 这个一致性我可以这么做

    另一方面这有些讨厌 因为即使我去掉一致性 我还是有这个被双精度获得的优先函数 它已经有了足够的接口 对吗?

    我们可能想要更加有选择性地 在双精度数值上添加东西

    所以即使我可以这样做 我不能用它来进行二进位检索所以这些 优先函数真的没有给我买入任何东西

    幸运的是我可以对哪些东西获得 优先函数应用程序界面更有选择性 通过使用Ordered的约束扩展

    所以这就是说一个是 Comparable的类型并被声明 为Ordered将能够 自动地满足优先要求 而这正是我们想要的

    抱歉 但我觉得这很酷 我们有了同样的抽象

    同样的逻辑抽象 来自于两个不同的地方 而我们已经使它们无缝协作 谢谢你们的掌声 不过我只是我觉得这很酷

    好 做好准备来一个味蕾清新剂了吗?

    这显示它起作用了 好 这是一个完全通用的 二进位检索的签名 作用于任意一个集合 带有适当的索引和元素类型

    现在我已经能听到 你们在那儿越来越不舒服了 我并不准备在这里写主体 因为这已经看起来很糟糕了 对吧 Swift 1有很多像 这样的通用免费函数

    在Swift 2中我们像这样 使用协议扩展将它们 变为方法 这很棒

    现在每个人都专注于在 这个调用站点的改善 它现在很明显地充满了 方法的精华 但随着这个家伙写二进位检索 我因其签名而爱上了它所做的事情 通过分离情况 这些情况下该方法应用于 声明的其余部分 现在读起来只是一个常规方法

    不再有尖括号盲区

    非常感谢

    好 在我们回到我们的故事前 来讲一下最后一个技巧

    这是一个包含最小模型的游戏场 关于Swift的心 OptionSetType协议

    它就是一个结构具有只读整型属性 叫做rawValue 现在来看一下一旦你昨晚 即可免费得到的广泛设置界面

    所有这些都来自于协议扩展

    如果你有机会的话 我邀请你来看一下 那些扩展是怎样声明的 在标准库中因为几个图层 一起工作来提供这个丰富的 应用程序界面

    好 这些就是你可以 使用协议扩展做到的一些很酷的事情

    现在 我想回到我们的图表示例中

    使值类型可相等

    为什么?因为我是这么说的 另外吃蔬菜

    不 事实上如果你想 知道为什么去听一下 周五的讲习会我已经跟你们讲过这个

    这是一个很酷的演讲 他们准备详细探讨这个问题

    总之Equatable对于大多数 类型来说都是很容易的 对吧 你只需要比较相应的部分 以求其对等性 就像这样

    但是现在我们来看一下图表发生了什么

    啊哦 我们不能对比两个 Drawable类型数组的对等性

    好吧 也许我们可以这么做 比较个体元素就好比这样

    好 我将给你们过一遍 首先你去顶它们有相同数量的元素 然后把两个数组压缩在一起 如果它们有相同数量的元素 那么你就找一对不相等的 好的你可以相信我的话 这还不是问题有趣的部分

    哦 对吧? 这是我们无法比较数组的整个原因就是 因为Drawable类是不对等的 因此两个数组之间没有等于运算符 我们没有等于运算符 给潜在的Drawable属性 我们能不能把Drawable 改为Equatable? 我们改变了设计像这样

    这里的问题在于 Equatable有Self要求

    这意味着Drawable 现在有了Self要求

    而Self要求正好将 Drawable 放进了同构中静态分派的世界

    但是图表需要的是 Drawable异构数组

    因此我们可以将多边形 和圆放在同一个图表中 那么Drawable必须待在异构的 动态分派的世界

    而这带来了矛盾 将Drawable改为 Equatable是行不通的

    我们需要这样做 就是说给Drawable添加 isEqualTo要求

    但是...不 我们不能用Self

    因为我们需要保持异构 而没有了Self 这就像用类完成Ordered一样 现在我们要让所有 Drawable 来处理异构比较情况

    幸运的是这次有一种方法可以做到

    与大多对称运算符不同 等式是特殊的

    因为有显而易见的默认的答案 如果类型不匹配的话 可以这么说如果有两个不同的类型 它们是不等的

    明白这一点我们可以实现 isEqualTo 为所有Equatable的 Drawable

    就像这样

    让我来向你们演示

    扩展就是我们所说的 它是给所有Equatable的 Drawable的

    好 首先我们有条件地将其他 类型向下转型到Self类型 如果这成功了然后我们可以继续下去 使用等式比较 因为我们有Equatable一致性 否则实例会被认为不对等

    那么大图片这里刚刚发生了什么呢?

    我们与Drawable的 实现器达成协议 我们说“如果你真的想 处理异构情况请做我的访客 去实现isEqualTo 但如果你只是想用我们 表达同构比较的常规方式 我们将为你处理所有异构比较的负担”

    所以在静态和动态世界之间建立桥梁 是极好的设计空间 我鼓励你们多多观察 我们使用等式的特殊属性 解决了这个特别的问题 但是问题并不都像这样

    你还可以做很多很酷的事情

    因此等式的这个属性不一定适用 但是什么才是普遍适用的呢?

    基于协议的设计

    所以我想在我们总结 何时使用类之前说几句 因为他们有他们的位置 好吗?有些时候你真的想要隐式共享

    例如值类型的基本操作 没有任何意义时 比如复制副本意味着什么呢? 如果你不明白这是什么意思 那么你可能想使它成为引用类型

    或者比较 一样的 这是作为数值的另一个基础部分 那么 比如窗口 复制窗口意味着什么呢? 你真的想看 一个新的图形窗口吗? 就在另一个窗口之上? 不知道

    这不会是你视图层级的一部分

    毫无意义

    另外一种情况 你的实例的生命周期依赖于 某些外部的副作用 比如磁盘上出现的文件

    部分原因是编译器大量地创建值 创建 再销毁 我们试图尽可能地优化 引用类型有这种稳定的特性 所以如果你要做一些对应于 外部实体的事情 你可能会想使它为引用类型 类 另一种情况是 抽象实例是“水槽” 比如渲染器

    我们灌注信息给它 给渲染器 我们让它画线

    举个例子如果你想做一个 TestRenderer 让它积累文本并将这些命令输出为 字符串 而不是直接将它们倒给控制台 你可以像这样做

    但要注意几件事情 首先这是最后

    其次没有基类 这仍然是一个协议 我使用协议来表达抽象

    好 额外的几种情况

    我们生活在面向对象的世界 对吧? Cocoa 和 Cocoa Touch致力于对象

    他们会给你基类 并期望你将它们编入子类 他们期望应用程序界面中的对象 不要与系统对抗 好吗? 这只会是徒劳的

    但是与此同时一定要谨慎 你知道程序中任何事物都不能过大 这对类和其他都适用 所以当你从类中重构或分解时 考虑使用值类型

    好了 总结一下

    协议 对于抽象来讲好过于超类

    第二协议扩展这个新特征 让你做很神奇的事情

    第三我提到让你们来听周五的演讲吗?

    来参加周五的演讲

    相关文章

      网友评论

          本文标题:(译) Swift 中的面向协议编程

          本文链接:https://www.haomeiwen.com/subject/jnedohtx.html