第十章:外观模式

facade

首先,让我们解决一下语言上的问题:字母Ç中的小曲线被称为“cedilla”,而字母本身被读成“S”,因此单词“façade”被读成“fah-saad”。欢迎在您的代码中使用字母ç,因为大多数编译器都能很好地处理它。

好了,现在,我们开始讨论这个模式……

我花了很多时间在量化金融和算法交易领域工作。正如您可能猜到的那样,一个好的交易终端所需要的是将信息快速地传递到交易员的大脑中:您希望尽可能快地呈现东西,没有任何延迟。

大多数财务数据(除了图表)实际上都以纯文本形式呈现:在黑屏上显示白色字符。在某种程度上,这类似于terminal/console/命令行界面在您自己的操作系统中的工作方式,但是有一个细微的区别

终端如何工作

终端窗口的第一部分是缓冲区(buffer)。这是存储呈现的字符的地方。缓冲区是一个矩形的内存区域,通常是一个1D[^1]或2D charwchar_t数组。缓冲区可以比终端窗口的可见区域大得多,因此它可以存储一些可以回滚的历史输出。

通常,缓冲区有一个指针(例如整数)来指定当前的输入行。这样,一个已满的缓冲区不会重新分配所有行;它只是覆盖了最老的那一行。

然后是视窗(viewport)的概念。视窗呈现特定缓冲区的一部分。一个缓冲区可以是巨大的,所以一个视窗只是从缓冲区中取出一个矩形区域并呈现它。当然,视窗的不能超过等于缓冲区的大小。

最后,还有控制台(终端窗口)本身。控制台显示视口,允许上下滚动,甚至接受用户输入。控制台实际上是一个外观:它是对幕后复杂设置的简化表示。

通常,大多数用户与单个缓冲区和视窗交互。但是,有可能有一个控制台窗口区域被垂直分割为两个视图,每个视图都有相应的缓冲区。这可以使用Linux下的screen 命令来实现。

注1: 大多数缓冲区通常是一维的。这样做的原因是,传递一个一维指针比传递一个二维指针更容易,而且当结构的大小是确定的和不可变的时,使用数组或向量没有多大意义。一维方法的另一个优点是,当涉及到GPU处理时,像CUDA这样的系统最多使用6维来寻址,所以过一段时间,从n维的块/网格位置计算一维索引就成了第二天性。

高级的终端

典型操作系统终端的一个问题是,如果你将大量数据输送到它里面,它的速度会非常慢。例如,Windows终端窗口(cmd.exe)使用GDI来渲染字符,这是完全不必要的。在一个快节奏的交易环境中,你希望渲染是硬件加速的:应该使用API(如OpenGL)字符让作为预渲染的纹理呈现在表面上。

一个交易终端由多个缓冲区和视窗组成。在典型的设置中,不同的缓冲区可能会与来自不同交易所或交易机器人的数据同时更新,所有这些信息都需要显示在单个屏幕上。

缓冲区还提供了比一维或二维线性存储更令人兴奋的功能。例如,TableBuffer可以定义为:

  1. struct TableBuffer : IBuffer
  2. {
  3. TableBuffer( vector<TableColumnSpec> spec, int totalHeight )
  4. {
  5. }
  6. struct TableColumnSpec
  7. {
  8. string header;
  9. int width;
  10. enum class TableColumnAlignment
  11. {
  12. Left, Center, Right
  13. } alignment;
  14. };
  15. } ;

换句话说,缓冲区可以接受某些参数并构建一个表(是的,一个很好的老式ascii格式的表!)并将其显示在屏幕上。

视窗负责从缓冲区获取数据。它具有以下特点:

  • 待显示缓冲区的引用
  • 大小
  • 如果视窗小于缓冲区,它需要指定它将显示缓冲区的哪一部分。它用绝对的x-y坐标表示
  • 视窗在整个控制终端的位置
  • 游标的位置,假设当前视窗在接收用户的输入,

外观在那里?

在这个特定的系统中,控制台本身就是外观。在内部,控制台必须管理大量不同的对象。

  1. struct Console
  2. {
  3. vector<ViewPort*> viewPorts;
  4. Size charSize, girdSize;
  5. };

通常情况下,控制台初始化也是一件非常棘手的事情。由于它是外观,它实际上试图提供一个真正可访问的API。这可能需要一些合理的参数来初始化所有的内部成员。

  1. Console::Console(bool fullscreen, int char_width, int char_height,
  2. int width, int height, optional<Size> client_size)
  3. {
  4. // 创建单个缓冲和视窗
  5. // 把缓冲和视图结合在一起并放入合适的集合中
  6. // 生成图像纹理
  7. // 网格大小的计算取决于我们是否需要全屏模式
  8. }

或者,也可以将所有这些参数打包到一个对象中,这个对象同样有一些合理的默认值。

  1. Console::Console(const ConsoleCreationParameters& ccp)
  2. {
  3. }
  4. struct ConsoleCreationParameters
  5. {
  6. optional<Size> client_size;
  7. int character_width{ 10 };
  8. int character_height{ 14 };
  9. int width{ 20 };
  10. int height{ 30 };
  11. bool fullscreen{ false };
  12. bool create_default_view_and_buffer{ true };
  13. };

总结

外观设计模式是一种将简单接口放在一个或多个复杂子系统前面的方法。在我们的例子中,可以直接使用涉及许多缓冲区和视窗的复杂设置,或者,如果你只想要一个带有单个缓冲区和相关视视窗的简单控制台,你可以通过一个非常容易访问和直观的API获得它。