设计
所谓 data-driven API,指的是用户可以把“操作”作为参数,传入函数,像下面这种:
stream = dataStream('load', 'example.csv');image = dataStream('get', stream, 1);newStream = processStream('map', @(im)(sobel(im)));
这是我最近在写的图像数据库读取的工具函数,我把视频或图片文件夹抽象成一个 dataStream,并基于这个 dataStream 完成一系列读写以及图像处理操作(processStream)。
代码中的 “load”(载入),“get”(读取),“map”(...就是你所知道的 map),都代表不同类型的操作,外层的 “dataStream”和 “processStream”仅起到一个入口的作用,它们的内部包含了每个操作的实现。设计 data-driven API 的方法很容易,例如上面的流处理函数:
function varargout = processStream(action, varargin) varargout = repmat({[]}, [1 nargout]); [varargout{:}] = feval(action, varargin{:});endfunction newstream = map(stream, action, varargin) % ......map 的实现(略)end
通过 varargin 和 varargout 传递可变的输入和输出参数,通过 feval 来调用对应的内部函数(例如map)即可。
至于可变参数的用法,可以参考下面两个文档:优点
Data-driven API 解决了 Matlab 的一个缺点,即一个文件仅能放一个主函数(以及几个用户无法访问的辅助函数)。它使相同功能的函数被良好的组织到了一起,许多图像处理库都采用这种风格的设计。
脱离 Matlab 来看,Data-driven 使得开发者可以在不改变接口的情况下新增功能(例如,只要往 processStream.m 文件里加新的内部函数,就可以自动扩展 processStream 支持的操作);
此外, data-driven 还可以让 API 支持某种程度的 ,因为操作的类型是通过字符串描述的,那么我们可以把这些字符串组织成一个文本文件,通过读取并执行这个文件,就能让 API 执行你想要的功能,或者我们可以把所需执行的操作保存到一个数组里,让 API 读取并执行这个数组。
例如,基于 processStream,我设计了一个流水线操作的函数 pipeline.m:
% 像流水线一样处理数据:% processStream('map',...) >>>> processStream('reduce',....)% 操作按照 schema 来执行,schema 就像下面这个,是个嵌套的 cell array% {% { 'map' , 'sobel', [3, 3] }, % 先在流中运行 3x3 的 sobel 算子% { 'map' , 'sharpen' }, % 在上一步的输出中运行锐化算子% ..........% }% 这个 schema 相当于一个小的 DSLfunction result = pipeline(stream, schema) for i = 1 : numel(schema) args = makeArguments(stream, schema{i}); result = processStream(args{:}); stream = result; endend% 构造 processStream 的输入参数function args = makeArguments(stream, operations) args = {operations{1}, stream, operations{2:end}};end
总结
我认为 Data-driven API 是 Matlab 中最重要的“设计模式”(如果它有设计模式的话...),通过它,我把图像数据库抽象成数据流,并在数据流上实现了 map, foreach,flatmap,reduce 等等一系列 functional 风格的操作,基于这些操作,我把一个原本很冗长的机器学习训练脚本变得十分简洁。