本节书摘来自异步社区《UML面向对象设计基础》一书中的第1章1.9节一般性,作者【美】Meliir Page-Jones,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.9 一般性
UML面向对象设计基础
一般性(genericity)指一个或多个类内部使用的类C的结构,仅在运行时(即示例类C的对象时)才提供。
说明一般概念的最好方式是讲述一个不堪回首的故事。当时我还是一名大学生,学习一门数据结构(Data Structures)的课程101。有一个学期,Rossini 教授给我们留了一项作业,设计和编程整数有序平衡二叉树(见图1.22)。平衡二叉树的主要特点是所有的叶子在同层上拉平。
在往树中插入另一个整数(如5 ,见图1.23)之前,一切顺利。插入整数5后,树变得不平衡,你不得不做一些痛苦的节点调整直到树再次平衡为止。
经过多次的桌面设计和联机调试,我们中间大多数人的算法可以正常运行了。带着满意的笑容交出程序去度假,花费几夜的努力去忘却那个有序平衡二叉树。
但不幸的是,Rossini教授的平衡树作业进一步扩大范围。作为下学期应用的一部分(商业应用101,Business Applications 101),需要将客户和产品形成排序列表。我们这些学习Data Structures 101的学生,还没有完全忘记有序平衡二叉树,径直调出老程序,复制二份。在一份副本中,用CustomerID替换Integer;在另一份副本中,用ProductID替换Integer。
这种对老程序的复制极大地提高了生产率。但这种方法并非十分理想,因为存在严重的复制隐患。这种隐患是现在我们不得不维护三个几乎相同的程序。
因此,如果我们发现了一个更好的平衡树算法,就不得不修改三个代码。这样不仅增加了额外的工作量,而且维护三个版本也有一定的难度(除非我们有自动的复制修改程序)。我们需要一种方法,只编写一次基本的平衡树算法结构,然后当我们需要对整数、客户、产品或其他进行处理时可以应用多次(不是简单的复制)。
此时此刻,一般性就像一匹疾驶而来的白马拯救了我们。如果将BalancedTree 定义为参数化类(正规称为一般类),则说明至少在BalancedTree中有一个类直到运行时才赋值(参数化类在C++中称为模板类)。可以猜到这个类就是我们实例化时,存储在特定平衡二叉树对象的节点中项所构成的类。
因此可以将类BalancedTree写为如下形式:
class BalancedTree<ClassOfNodeItem>;
…
var currentNode:ClassOfNodeItem.New;
…
currentNode.print;
…
注意参数化类的参数ClassOfNodeItem。这是一个形式参数,其实际“值”在运行时提供。例如,当实例化类BalancedTree的一个新对象时,将提供一个真正的类名作为参数,如:
…
var custTree:BalancedTree :=BalancedTree.New<Customer>;
var prodTree:BalancedTree :=BalancedTree.New<Product>;
…
因此,custTree现在表示一个在节点中保存类Customer实例的对象(即BalancedTree的实例),如图1.24所示。当然对于prodTree是类似的。
下面的程序看起来好像将第一段程序复制了两次(一次是为Customer,一次是为Product):
class BalancedCustomerTree;
…
var currentNode:Customer:=Customer.New;
…
currentNode.print;
…
class BalancedProductTree;
…
var currentNode:Product:=Product.New;
…
currentNode.print;
…
最后,注意currentNode.print语句。这是多态的一个好例子,因为当我们在参数化类BalancedTree中写这个语句时还不知道currentNode的类是什么。因此,实例化BalancedTree时操作print应根据用于实例化特定树的类而定义。
另举一个例子,如果你设计一个参数化类HashTable,则应指出任何向C提供实际参数的类(如Symbol)必须定义操作hash 。在第12章详细讨论一般性可能带来的危险。
你可能意识到一种不用一般性也不用复制编写BalancedTree的方法。可以让平衡树的节点接受一个超类/子类层次结构中最顶层的类对象。如果将该类命名为类Object,则该代码段为:
class BalancedTree;
…
var currentNode:Object:=Object.New;
…
currentNode.print;
…
现在平衡树的每个节点将接受任何一个对象的加入。甚至可以将客户、整数、产品、多边形、牛仔及马混在相同的树中。这几乎没有什么意义。更糟的是让这些不同的对象类理解消息print是不太可能的。
BalancedTree和HashTable都是容器类(container class)的例子。容器类用在某些(通常比较复杂)结构中保存对象。一般性常用在面向对象中设计这样的容器类。尽管不是必须使用一般性为容器类编写可重用代码,但它确实比复制的程序或将任意类的对象混合在同一个容器的脆弱设计要好。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。