数据结构与程序架构(四)

来源:互联网 发布:软件测试知识点总结 编辑:程序博客网 时间:2024/05/01 22:14

在上节中,我们讨论了“以需求驱动”的设计思想和算法,现在我们来举个具体例子,以帮助我们更好的理解这种设计思想。

下图是一个典型的媒体播放器程序的数据流图:

图1:一个典型的媒体播放器数据流图


图中的每一个圆圈代表一个处理单元,不难发现,图中的 video renderer和 audio renderer 是这个程序的两个最终输出,如果我们只用一个调度程序(即框架程序)来处理这个拓扑结构的话,显然,我们只需要轮流将这两个处理单元作为输入,传递给调度程序,而后由调度程序(按“以需求驱动”的算法)来驱动这个程序的执行。现在就让我们以视频流的处理为例(或音频流的处理,显然它们的流程是完全相同的),来看看我们的程序是如何工作的。

首先,我们的调度程序需要根据数据流的流向,产生一个“需求树”,见下例:

[Video render]

    |_[Sync clock]

    |_[VideoDecoder]

        |_[Stream demuxer]

            |_[File parser]

然后我们按“深度优先”来遍历这个棵树,并且计算出每个节点的输出,同时自动将输出数据传递到上级节点的输入缓冲区中。当计算根节点(即 video renderer)的输出时,因为 video renderer 总是根据 sync clock 的输出值判断是否需要输出这一帧,如果需要,它就将数据送入显示设备,并且清除所有输入缓冲区;如果不需要,则保留 video decoder 对应的输入缓冲区,仅清除 sync clock 对应的输入缓冲区,这样,当它第二次执行时,因为它的 video decoder 对应的输入缓冲区中仍有数据,所以它的需求树将只包含一个 Sync clock 的子节点,因此我们不必每次都触发 video decoder 及其“需求”链。需要注意的是,以上清除缓冲区的行为既可以在 video renderer 中实现,也可以在调度程序中实现,但需要增加处理单元的接口,以告知调度程序应清除哪些缓冲区。

通过上面的分析,我们应该不难理解整个程序的执行过程。现在让我们来试着修改这个程序的需求,比如我们希望增加一个“缓存”的功能,此时,我们仅仅需要修改 video renderer 的实现,或者,在 video renderer 和 video decoder 之间增加一个“缓存”的处理单元,而我们的调度程序完全不需要修改;再比如,我们需要增加对网络流媒体的支持,我们只需要增加一个 net stream parser 的处理单元和一个 source selector 的处理单元,并将 file parser 和这个 source selector 相连,然后通过 source selector 与 stream demuxer 相连,而这些修改也不需要修改我们的调度程序。还有许许多多其它的例子,在此就不一一列举了,从中我们不难体会,这些修改或扩展都仅仅局限于整个程序的一部分,且通常可以独立为一个处理单元(或下级处理单元,如果每一个处理单元内部也采用相同设计的话),因此我们也非常容易检查它的正确性,并做单元测试。

另外,我们也可以对我们的调度程序单独进行优化,因为它不依赖于程序拓扑的具体结构,它只关心最终输出单元,以及产生“需求树”的过程。而且,我们甚至可以引入“元类编程”技术,在这个调度程序中加入“切入点”的支持(请参见《C++语言中的元类编程》系列文章),我们就可以动态地追踪整个程序(指由处理单元组成的拓扑结构)的执行过程了,这对我们调试和维护整个程序也有非常大的好处。

2 0
原创粉丝点击