软件设计的哲学: 第六章 更深的通用模块
目录
在设计新模块时,最常见的一个决定就是以通用方式还是特殊方式实现它。有些人可能会说,应该采用通用的方法,即实现一种机制,用于解决广泛的问题,而不仅仅是当前重要的问题。在这种情况下,新机制可能会在未来发现意想不到的用途,从而节省时间。通用方法似乎与第3章中讨论的投资心态一致,即您预先花费更多的时间来节省以后的时间。
另一方面,我们知道很难预测软件系统的未来需求,所以一个通用的解决方案可能包含一些实际上并不需要的设施。此外,如果您实现了一些过于通用的东西,那么它可能无法很好地解决您现在面临的特定问题。因此,有些人可能会说,最好关注今天的需求,只构建您知道自己需要的东西,并根据您今天计划使用它的方式进行专门化。如果采用特殊用途的方法,并在以后发现其他用途,则始终可以对其进行重构,使其成为通用用途。专用方法似乎与软件开发的增量方法一致。
6.1 使类具有一定的通用性
根据我的经验,最好的方法是以某种通用的方式实现新模块。短语“某种程度上通用的”意思是模块的功能应该反映您当前的需求,但是它的接口不应该。相反,接口应该足够通用,以支持多种用途。该接口应该易于使用,以满足今天的需要,而不是专门针对他们。“有些”这个词很重要:不要忘乎所以,不要构建一些通用的东西,因为它很难满足您当前的需求。
通用方法最重要的(可能也是最令人惊讶的)好处是,它比专用方法产生更简单、更深入的接口。如果您将该类用于其他目的,那么通用方法还可以在将来为您节省时间。然而,即使该模块仅用于其原始目的,由于其简单性,通用目的的方法仍然更好。
6.2 示例:为编辑器存储文本
让我们考虑一个来自软件设计课程的例子,在这个课程中,学生被要求构建简单的GUI文本编辑器。编辑器必须显示一个文件,并允许用户指向、单击和键入来编辑文件。编辑器必须在不同的窗口中支持同一文件的多个同步视图;他们还必须支持多级撤销和重做文件的修改。
每个学生项目都包含一个管理文件底层文本的类。文本类通常提供将文件加载到内存、读取和修改文件文本以及将修改后的文本写回文件的方法。
许多学生团队为text类实现了特殊用途的api。他们知道这个类将在交互式编辑器中使用,所以他们考虑了编辑器必须提供的特性,并根据这些特定的特性定制了文本类的API。例如,如果编辑器的用户键入退格键,编辑器将立即删除光标左边的字符;如果用户键入删除键,编辑器将立即删除光标右侧的字符。了解了这一点,一些团队在text类中创建了一个方法来支持这些特定的特性:
void backspace(Cursor cursor); void delete(Cursor cursor);
这些方法中的每一个都以光标的位置作为参数;特殊类型游标表示此位置。编辑器还必须支持可以复制或删除的选择。学生们通过定义一个选择类,并在删除期间将这个类的对象传递给text类来处理这个问题:
void deleteSelection(Selection selection);
学生们可能认为如果text类的方法对应于用户可见的特性,那么实现用户界面会更容易。然而,在现实中,这种专门化对用户界面代码几乎没有什么好处,而且它为用户界面或文本类的开发人员带来了很高的认知负荷。text类以大量的浅层方法结束,每个浅层方法只适合一个用户界面操作。许多方法(如delete)只在一个地方调用。因此,开发用户界面的开发人员必须了解文本类的大量方法。
这种方法在用户界面和文本类之间造成了信息泄漏。与用户界面相关的抽象,如选择或退格键,反映在文本类中;这增加了开发人员处理文本类的认知负荷。每一个新的用户界面操作都需要在text类中定义一个新方法,因此处理用户界面的开发人员可能最终也要处理text类。类设计的目标之一是允许独立地开发每个类,但是专门化的方法将用户界面和文本类绑定在一起。
6.3更通用的API
更好的方法是使text类更通用。它的API应该只根据基本的文本特性来定义,而不反映将用它实现的高级操作。例如,只需要两个方法来修改文本:
oid insert(Position position, String newText); void delete(Position start, Position end);
第一个方法在文本中的任意位置插入任意字符串,第二个方法删除大于或等于开始但小于结束位置的所有字符。这个API还使用了一个更通用的类型Position而不是游标,它反映了一个特定的用户界面。text类还应该提供一些通用的工具来处理文本中的位置,例如:
Position changePosition(Position position, int numChars);
此方法返回一个新位置,该位置距离给定位置有一定数量的字符。如果numChars参数为正,则新位置在文件中的时间晚于位置;如果数字是负数,则新位置在位置之前。该方法在必要时自动跳转到下一行或上一行。使用这些方法,可以用以下代码实现delete键(假设游标变量保存当前游标位置):
text.delete(cursor, text.changePosition(cursor, 1));
同样,backspace键可以实现如下:
text.delete(text.changePosition(cursor, -1), cursor);