面向价值的编程

2020-05-03 04:19:48

在WWDC 2015上,Dave Abrahams在一个非常有影响力的主题为“SWIFT中面向协议的编程”的会议上解释了如何使用SWIFT的协议来克服类的一些缺点。他建议这样一条规则:“不要从上课开始。从协议开始。“。

为了说明这一点,Dave描述了一种面向协议的原始绘图应用程序方法。该示例从几个基本体形状开始工作:

协议可绘制{}结构多边形:可绘制{var角点:[CGPoint]=[]}结构圆:可绘制{var center:CGPoint变量半径:CGFloat}结构图:可绘制{var元素:[可绘制]=[]}。

在传递对象时,对象的引用语义增加了复杂性。在一个位置更改对象的属性可能会影响有权访问该对象的其他代码。并发性需要锁定,这增加了大量的复杂性。

通过继承重用代码是很脆弱的。继承还将接口耦合到实现,这使得重用更加困难。这是它自己的主题,但即使是OO程序员也会告诉您“组合胜过继承”。

对于子类,很难精确识别类型。例如,使用NSObject.isEquity()时,必须注意仅与兼容类型进行比较。协议使用泛型精确标识类型。

为了处理实际绘制,添加了一个渲染器协议,该协议描述了基本绘制操作:

协议渲染器{函数移动(至p:cgPoint)函数线(至p:cgPoint)函数弧(中心:cgPoint,半径:cgFloat,起始角度:cgFloat,结束角度:cgFloat)}。

协议可绘制{函数绘制(_renender:renender)}扩展多边形:可绘制{函数绘制(_渲染器:渲染器){渲染器。移动(到:角落)。最后!)。对于角中的p{渲染器。Line(to:p)}扩展圆:可绘制{函数绘制(渲染器:渲染器){渲染器。圆弧(在:中心,半径:半径,开始角度:0.0,结束角度:两个圆点)}}扩展图:可绘制{函数绘制(渲染器:渲染器){对于元素{f.。绘制(渲染器)}。

这使得定义不同的渲染器成为可能,这些渲染器可以轻松地处理给定的类型。一个主要卖点是定义测试渲染器的能力,它允许您通过比较字符串来验证绘图:

struct测试渲染器:渲染器{func move(to p:cgpoint){print(";moveto(\(p.。x)、\(p.。y))";)}函数行(到p:CGPoint){打印(";lineto(\(p.。x)、\(p.。y))";)}函数圆弧(中心:CGPoint,半径:CGFloat,起始角度:CGFloat,结束角度:CGFloat){Print(";Arcat(\(Center),半径:\(半径),";+";开始角度:\(开始角度),结束角度:\(结束角度))";)}}。

扩展CGContext:渲染器{//CGContext已有`Move(To:CGPoint)`函数行(To p:CGPoint){addLine(To:p)}函数弧(中心:CGPoint,半径:CGFloat,起始角度:CGFloat,结束角度:CGFloat){addArc(中心:中心,半径:半径,起始角度:起始角度,结束角度:结束角度,顺时针:真)}}。

扩展渲染器{函数圆(在中心:CGPoint,半径:CGFloat){arc(在:中心,半径:半径,起始角度:0,结束角度:两个圆点)}}。

我认为这种方法相当有说服力。它的可测性要强得多。它还允许我们通过提供单独的渲染器来以不同方式解释数据。而且,值类型巧妙地避开了面向对象版本可能会遇到的许多问题。

尽管有这些改进,但在面向协议的版本中,逻辑和副作用仍然紧密耦合。Polygon.raw做两件事:它将多边形转换为多条线,然后呈现这些线。因此,当需要测试逻辑时,我们需要使用TestRenender-尽管WWDC Talk暗示了这一点,但它是一种模拟。

扩展多边形:可绘制{函数绘制(_渲染器:渲染器){渲染器。移动(到:角落)。最后!)。对于角中的p{渲染器。线路(至:P)}。

在这里,我们可以通过将逻辑和效果分成不同的步骤来将它们分开。让我们声明表示底层操作的值类型,而不是使用MOVE、LINE和ARC的渲染器协议。

枚举路径:Hasable{struct Arc:Hasable{var center:CGPoint var Radius:CGFloat var startangle:CGFloat var endangle:CGFloat}结构线:Hasable{var start:CGPoint var end:CGPoint}//替换`Arc(at:CGPoint,Radius:CGFloat,startangle:CGFloat,endangle:CGFloat)`案例圆弧(Arc)//替换`。

现在,Drawables可以返回一组用于绘制它们的路径,而不是调用这些方法:

协议可绘制{