三、通过构建树来搜索设计模式
我们的解决方案是实现为一个名为DPRecog的Java工具,它提供了几个类,其中包括类Explorer、Graph和Recog。类资源管理器分析输入应用程序的字节码并构建相应的图。该类的Method explore()按指令扫描字节码指令,然后在任务结束时返回一个类图实例。
A:探索与图形表示
探索和图表示类图持有映射类及其关系的邻接矩阵的表示。这种表示方式允许我们在固定的时间内执行大多数需要的操作。此外,由于采用了一种编码,我们避免了在大量类的情况下产生内存浪费(否则会带来一个很大的矩阵)。编码允许每个元素只有一个字节。这样,一个包含n个类的软件系统将占用最多n平方个字节,即使对于大型系统来说,这也是一个非常小的内存量。编码包括使用2的幂表示类之间八种不同关系中的一种(继承=1、使用=2、方法调用=4、实现=8、方法参数类型=16、返回类型=32、覆盖=64、实例化=128)。如果给定的一种关系存在于两个类之间(也就是说一种连接图中两个节点的边),那么矩阵中该元素的二进制表示在适当的位是1;当然,否则会出现0。
B:为设计模式角色构建类集
每个被分析系统类的都被分配到一个集合,根据这个集合,类可以在设计模式中扮演某个角色。只有当类与其他类的所有关系都保持在设计模式中时,类才被认为在设计模式中扮演角色。可以通过我们构建的图检索足够多的元素,以了解是否满足这些条件。我们将特别关注任何顶点的入边和出边。因此,几个集合中填充了合适的类,每个集合对应于一个设计模式中的角色。例如,对于设计模式观察者,将构建一个集合,其中包含系统中可以扮演角色Subject的所有类,另一个集合包含角色concrete tesubject的类,等等。集合的数量与设计模式规定的角色数量相同。当然,集合越小,搜索速度越快。在我们的实现中,每个设计模式模型都由专用的Java线程处理,因此在多核主机中会同时检查更多的设计模式。构建这样的集合将有助于大大减少在搜索设计模式时要执行的检查数量。填充集合的过程和搜索本身都由类Recog实现。集合由数组表示,fillset()方法完成了用适当类的id填充集合的任务。为此,我们使用在探索阶段存储在Graph类中的数据(每个顶点与两个字节相关联,一个用于描述传入边的类型,另一个用于输出边的类型)。图1左边显示了一个简单的应用程序,它表示为一个图形,其中每个节点是一个类,每个边是类之间的关系。关系(和角色)使用不同的颜色显示,参考右边显示的设计模式。因此,设计图案的每一个角色都用相应的颜色标记在应用图形中,设计图案中的每一个关系也标记在应用图形中。给定的表示非常简单,而被分析的实际系统将拥有更多的类和关系。此外,所有需要的设计模式都必须建模。
C:整体设计模式匹配
一旦角色的集合被填满,每个集合的元素将被定位为树的节点。我们将每组树级别。因此,第一组的元素将是可能的根节点,第二组的元素将是可能的第二层次,然后我们对每棵树执行一个遍历,包括检查以确保之间的匹配定位的边缘类和相关的设计模式模型。如果只有一个检查失败,那么树的整个分支将被截断。如果遍历继续,并且所有检查都成功,直到叶节点,我们将存储匹配。搜索结束时,所有可能的路径已被探索。图2显示了上述针对简单应用程序和图1所示设计模式模型的搜索算法。该算法(即迭代树遍历)由类Recog的accept()方法实现。对于路径中的每个顶点,都会调用vertexSuits()方法来执行关于边缘的所有检查。对在当前状态下通过所有检查的顶点的引用被推送到堆栈结构中,这样,当路径完成时,它的元素可以作为设计模式的出现而简单地存储。
D:性能优化
在遍历开始之前,集序列代表的角色充满了候选人类(在我们的实现中是数组)的序列是有序提升基数,以便包含根节点集的最小的数是元素和集合包含叶子节点拥有最大的一个。也就是说,我们通过对最小的集合给予最高的优先级来确定每一层的节点。虽然这样的顺序并不影响将被发现的对应关系,但是性能得到了极大的提高。一个简单的观察可以帮助我们理解执行时间如何通过重新排序集合而改变。通常,在设计模式中扮演角色的类的组合数量远远少于类可能组合的总数。这意味着大多数树路径将在搜索过程中被截断。因此,最好的策略似乎是从最小的集合开始进行探索。很可能,这种选择将允许我们在只执行少量检查之后,很快截断无用的路径。如下所示,实验数据已经证实,通过这种优化,我们已经明显提高了搜索的效率。