了解最新技术文章
上次我向您介绍了SFont
部分定义(“混合”)字体的简单但有用的表示形式:
在我们的术语中SFont
被称为“属性”,而它的成员m_ostrName
和m_onSize
则被m_obBold
称为属性的“方面”。我们讨论了如何SFont
将其视为表示一组字体或所有可能字体空间中的子空间,并利用这些见解来定义一些有用的方法和函数。
上一篇博客文章的重点是为您提供一些概念和观念,帮助您设计自己的属性,而不仅仅是字体。在我们的软件中,我们有很多遵循相同模式的属性:字体、颜色、填充、线条、项目符号、标记(在散点图和折线图中用于标记数据点),甚至精度(十进制数字的格式) 。今天我们将了解行格式与字体有何不同,以及如何修改简单的数据结构以使其适合行格式。SLineFormat
让我们从基于 PowerPoint 的 LineFormat API的 的直接定义开始:
看起来很简单,但是看不见的线的破折号样式的含义是什么?权重为 0(零)的“可见”线与“不可见”线有何不同?满足隐藏状态的概念:我们将隐藏状态定义为在数据结构中表示和维护的状态(信息),但用户无法辨别。每个设计数据结构的人都经常遇到隐藏状态,无论他们喜欢与否。在许多情况下,人们很容易为了一些表面上的简单性和明显的对称美而忽略隐藏状态,或者仅仅是因为开发人员未能识别出后来证明是隐藏状态的东西。
隐藏状态不一定是坏事:考虑使用某种特意选择的字体在 PowerPoint 文本框中键入文本。当由于某种原因您决定重写文本时,第一步可能是删除现有文本并返回到空文本框。空文本框没有任何字体的视觉表示(不考虑闪烁光标的大小),但您期望当您开始键入时,您的文本将使用之前使用的相同字体。这种行为很有用,实现它需要刻意使用隐藏状态。
但在大多数情况下,隐藏状态更令人困惑而不是有帮助。通常,隐藏状态以数据模型历史遗迹的形式出现。在我们的产品领域中,如果我们不小心隐藏状态,两个图表可能看起来“像素级”相同,但当用户更改基础数据时,可能会表现出非常不同的行为。如果用户从第三方收到这些图表并且无法知道它们是如何创建的,这尤其令人恼火。
“无用的隐藏状态”的实例通常被称为“冗余”,数据结构设计的一般经验法则是冗余是不好的,应该避免。我们是否忽略了 中的任何隐藏状态SFont
?不,我们没有:我们对SFont
财产的定义的所有方面都是相互独立的。每个方面本身都是有意义的,无论其他方面的价值如何。借用向量空间的概念,我们说字体的名称、大小和粗细是相互正交的。
回到SLineFormat
,我们发现一条不可见的线有多种表示形式。鉴于所有不可见线对用户来说看起来都是一样的,我们可以有把握地说这是隐藏状态的一个实例。此时我们需要确定这个特定的隐藏状态是有用还是有害。我可以立即说出一些常见的用例,在这些用例中,不可见线的模糊表示是有害的:
当在用户界面中显示可用线条格式列表时,我们希望在列表中最多显示一个不可见线条格式的实例。为此,不可见行格式必须与其他不可见行格式进行“相等”比较。
当显示多选行的当前行格式时,所有行都不可见,用户界面应反映“不可见”而不是“混合”,请参见方法SFont::Union(SFont)
。
当确定在另一种行格式之上应用一种行格式是否会产生任何影响时,我们不应该得出这样的结论:将一种不可见行格式应用于另一种不可见行格式会导致有意义的更改,请参阅方法SFont::IsSupersetOf(SFont)
。
同时,我发现很难提出任何用例来证明以不可见行格式维护隐藏状态是合理的。因此,为了本文的目的,我们假设SLineFormat
不应包含任何隐藏状态。
我们怎样才能做到这一点?首先,成员m_obVisible
和存在冗余m_onWeight
。它们中的任何一个都可以指示不可见的行格式,因此理想情况下只需要其中一个。显然,m_onWeight
是表示可见行格式所必需的,但m_obVisible
不会提供任何无法从 获得的信息m_onWeight
,因此m_obVisible
应该被删除:
微软MsoLineDashStyle
和MsoLineStyle
枚举没有“不可见”或“无线”的值,但仍然存在不可见线的冗余表示。那个怎么样?好吧,如果一个线格式有m_onWeight==0
,即它代表一条不可见的线,m_omsolinedashstyle
并且m_omsolinestyle
可能仍然有值。因此 的成员中有很多不同的值组合SLineFormat
,它们都可以表示“no line”。
我们怎样才能控制这些无意义的价值观组合呢?至少有两种可能的方法:
我们可以忽略其中的任何值m_omsolinedashstyle
以及m_omsolinestyle
每当我们遇到时m_onWeight==0
(参见清单 4),
或者我们可以确保每当 时m_onWeight==0
,其他成员始终具有一个且相同的唯一值(参见清单 5)。
虽然忽略不相关方面的值在理论上听起来很简单且最规范,但在实践中事实证明,确保无意义方面的特定值可以实现更干净、更简单、更规范的代码。干净、简单、规范的代码意味着更少的错误和更好的可维护性,因此我们在我们的软件中主要使用后一种方法。特别是,由于我们属性的设计方式,std::nullopt
无论如何,每个方面都方便地具有“未定义”( ) 状态。为此目的使用“未定义”状态可以使我们免于处理任意魔法值(当然,原则上这也是可行的)。
有了对依赖(非正交)属性方面的基本了解,让我们看看其他一些关键方法和函数的实现SLineFormat
:
现在,我们可以使用 (6)operator<<(...)
来编写链式表达式,就像使用 那样SFont
,它简单、优雅,但正如我们将在下面看到的,它是错误的(引自 HL Mencken)。举个例子,让我们再次看看我们的产品域:我们有一个可见线的全局默认线格式,并且我们有一个用于特定目的的部分定义的默认线格式的层次结构。我们如何为条形图中突出显示的部分的轮廓编写默认值?
这个表达式非常适合SFont
,那么用它来做什么有什么问题SLineFormat
呢?假设我们从一些合理的全局默认行格式开始。一些现代的图表样式使用没有轮廓的彩色区域,因此lineDefaultSegment
可以设置为明确的不可见轮廓。突出显示一个片段可能会证明一个特别粗的轮廓(甚至可能是红色的,但我们的玩具示例中不支持颜色),并且它应使用全局默认值中定义的任何内容MsoLineDashStyle
。MsoLineStyle
因此,我们的默认值可能如下所示:
如果我们将清单 (8)中的值输入到表达式 (7)中,则结果是部分定义的行格式:{/monWeight/ 12, /momsolinedashstyle/ std::nullopt, /m_omsolinestyle/ std::nullopt}
。显然,这没有用:为了在突出显示的段周围显示一条线,仅知道粗细是不够的。我们还需要一些明确定义的MsoLineDashStyle
和MsoLineStyle
。msoLineSolid
和到底在哪里msoLineSingle
迷路lineDefaultGlobal
了?答案是:结合性。
C++ 状态的运算符优先级规则<<
是从左到右计算的。如果我们在操作链中遇到一种不可见的行格式<<
,那么根据清单(6),我们清除所有隐藏状态。然后,当我们使用另一种行格式(部分定义可见)时,我们无法从最左边的操作数恢复丢失的方面。这很容易解决:我们需要从右到左评估链:
24小时免费咨询
请输入您的联系电话,座机请加区号