如何利用多级抽象思维来设计库?

更新时间:2015-03-17 10:32:12点击次数:2124次

摘要:开发者在设计库时,应遵循如下几大原则:迭代设计、可组合的、避免回调、抽象级别。此外,在你的库中,应该提供高级函数来帮助用户处理80%的任务。对于剩下的15%,应该提供一个低级API。


之前,我们曾发表《函数式语言库模式:框架是魔鬼?》该文论述了库与框架之间的区别,及如何设计组合化的库。而本文作者在此之前,还发表了一篇《Library patterns: Multiple levels of abstraction》,结合具体实例,向大家非常详细地介绍了库设计模式及库设计中的多级抽象思想。


以下为具体译文:


库设计模式


对于库设计理论来说,有几点在我的实际工作过程中体会深:


迭代设计——首先,不要为了一个库而设计库。在F#中,你可以把多个功能都放入一个脚本然后按需进行引用或复制至其它项目。这是好的库需求分析途径。一旦你想出更好的点子,就可以把它以文件形式加入到一个新项目中;

可组合的——可组合性是函数式编程的关键理论,其重要性等同于库设计。一个库应当可以让用户以简单的方式来进行二次开发,实现更多更复杂的功能;

避免回调——回调是很容易影响可组合性的。当你编写一些复杂的函数时(例如处理Markdown文档),你可能会受到诱惑而进行参数化回调(例如置入一个预处理器来对文档进行解析和翻译间的转换)。回调的问题是会使你的代码被加上太多的结构。这不但不会带来灵活性,反而随着回调的增多而使设计变得更加复杂;

抽象级别——那么我们怎么才能找到简单易用的API并在不同场合进行使用?问题的关键是能提供多级抽象。


我认为上述几点是能影响库设计好坏的。本文将先就抽象级别一点展开论述。 


库是如何被使用的?


每个库都对应着一定的典型应用场合。比方说,F# Formatting格式工具可以对一个目录下的所有文件进行文档生成,这占到使用频率的80%。有时我们可能需要以不同的方式来处理个别文件(例如使用不同模板)。一个完整的库应能兼顾该需求。还有某些时候我们需要以别的方式来处理某个文件,如添加一个自动生成内容表(TOC)。


对于类似的情况,我找到一种行之有效的处理方法—以多级功能抽象的方式来创建库。在高级,单一个函数调用应处理80%的应用场合。然后如果有需要,你可以再多建一级来处理额外15%的应用场合。后如果还有需要,就再多建一级来处理后4%的应用场合。对于后的1%,我的建议是发送一个pull request!


该设计模式在核心功能库中是被深度采用的,例如F#链表库。遵循该模式,还能使我们的库成为领域的特定语言从而更具可读性。 


示例#1:链表


用链表来阐述多级抽象是很有代表性的。


高级:高频函数

在高级抽象,可以使用高频函数来对链表进行处理(或在C#中使用LINQ)。例如从0到100的整数中获取正的sin值链表,可以这样编写: 


类似List.map和List.filter的高频函数在链接处理中有80%频率会用到甚至更高。但有时我们可能需要非高频操作。


低级:递归和模式匹配

例如根据符号变更对链表进行分拆,例如把[1; 4; -3; 2]分为[1; 4]和[-3; 2]。如果不借助低频API以递归模式匹配进行处理,单靠高频函数是很难实现的:


在loop中,进行了三种处理:


全部元素都是同符号;

发现有一个符号发生变更;

在符号变更前已经遍历所有元素。

如果是使用C#中的IEnumerable<T>,或许操作起来略显复杂,但是仍有一些低级API可供使用(使用GetEnumerator进行临时集合和转化)。 


从低级转到高级

集合API设计的好处是可以实现从低级到高级的转换。splitAtSignChange函数在根据邻近函数值进行拆分时,可以看成是一个更综合操作的一个实例:


该函数与前个版本十分相似—不同的是增加了额外的参数f来判定什么时候断开链表。虽然函数本身使用了低级API,但提供了转为高级的途径。

再回头看高级的定义是能包含更简单的编程方式。也就是说,根据X轴的临界值来对从1到10的sin值进行链表进行拆分: 

可见这就实现了到高级的转换—使用两个简单明了的函数处理拆分问题。


示例#2:3D的领域特定语言


这是另一个典型的例子,特别是在进行自定义对象建模时。

高级:创建城堡

其实现代码如下:

 


在高级抽象层面,我们仅仅使用4行代码就把城堡创建好了!但这仅能进行非常受限制的创作(规矩的城墙和塔组成的城堡),或许我们想做得更多。


高级:3D对象组合

如果想使用不同的塔外形该如何处理呢?要查看低级3D渲染代码吗?不必。tower函数本身就是根据另一种语言或抽象等级来进行编写的。一个塔就是一个填色后的圆柱体加上一个填色的有正确朝向的圆锥体: 

 

使用库时,一开始是高级抽象的,但熟悉之后,我们可以进入下一级。在这级中,我们可以打造自己的语言(抽象)例如前述的List.splitAt。


低级:使用OpenGL进行面部渲染

在进行3D渲染时存在一个低级操作。以该库为例,调用OpenGL原始操作是低级的(使用OpenTK包装),这稍微有点复杂: 

 

如果想以更简单的方式来生成图形,我们可以在3D原始操作和渲染之间加多一层,但这不是我们要讨论的。即便如此,我们还是可以看出多级抽象的重要性。明显的一点是可以从已创建的事物中发现如何实现高级到低级的转换(例如塔是如何组成的),然后以其它方式使用低级原始操作。


换言之,如果你直接以调用OpenGL的方式来生成塔,那么是很难再生成其它形态塔的。但如果有多级抽象机制,这就不是问题了。


综述


本文主要论述了库设计中的多级抽象思想。在你的库中,应该提供高级函数来帮助用户处理80%的任务。对于剩下的15%,应该提供一个低级API。关键的一点是高级API可根据低级API来呈现。这样不但满足了日常需求,还给用户留有二次开发的空间,是更加吸引和友好的。 (编译/伍昆 责编/张红月)


英文来自:Tomas Petricek's blog

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息
  • 项目经理 点击这里给我发消息