清明小憩之后,我们再续前文。
    上篇文章中有一段剖分带洞多边形的录像,在绘制好任意多边形后,点击一些按钮可以查看整个剖分的各个步骤,本篇就详细讲述这个步骤显示功能的实现方式。
    每个人在设计类结构时都会有自己的习惯,我的方式是在一片空白时去构想我将要实现功能的各个参与者,它们各自担任的角色以及负责的功能,在这个过程中还不需要考虑接口,更不用考虑代码,只是考虑某个参与者是否有必要并且明确它所承担的责任。我发现将每个参与者拟人化是很有益处的,如果将其拟成某种固有属性与其承担责任非常相近的具体物件则更有帮助,这个过程我称之为形象化,一个著名的例子当属网络爬虫。当你将每个参与者形象化之后,你将发现现在的程序不再是一块块死气沉沉的代码,而变成了一组生机勃勃的团队,他们的目标与分工都是如此明确。
    这种方式还可以再进一步,形象化时可以将某些参与者弄的憨态可掬一些,当它上场时它的某些行为很可能就惹得你忍俊不禁,这对提升一个开发者的激情很有好处,对厘清某个复杂的逻辑也颇有裨益,在《第七回:寻找三角形,夺取红旗》中你将看到我的爬虫是怎样在辨别果壳与果核的过程中帮我找到一个个梯形的。

一、设计类结构
    在设计本文主题——步骤显示功能的结构时,正是运用了这种形象化的设计方式。首先在一张空白的纸上我画上了一个管理所有显示步骤的集合类ShowStepCollection,该类的角色好似一个看管学校的大爷,如果想找某个显示步骤,不要试图去直接访问,问问大爷就可以找到,当然如果你要添加一个显示步骤也要通过这位大爷,大爷会为他安排好一切。这个类将步骤显示功能封装到了背后,在程序中如果希望访问显示步骤都要到这里来,可以认为该类是一个门面。
    下面要考虑的就是这个集合类中要存储的东西,看看上篇文章中的录像你会发现步骤的显示是以每条扫描线进行分组的,所以在此集合类中要有一个显示步骤组ShowStepGroup的集合,而每个步骤组中再包含具体的显示步骤ShowStep,经过这样一系列的包装,显示步骤的存储就算完成了,下面给出了类图。
 


    显示步骤多种多样,比如显示左辅助线、显示扫描线、显示交点、显示三角形等等,而他们各自的处理方式显然不同,这样需要用到面向对象的一个最基本的特性——多态,因而ShowStep是一个抽象类。在上面的设计过程中,体现了针对抽象编程的设计原则。

二、设计接口
    类结构搭建好后,下一步就是让他们协调工作起来,而协调的媒介正是接口。
    ShowStepCollection需要添加显示步骤组以及添加显示步骤,所以需要addGroup和add两个函数,为了能够访问各个步骤组,需要Count、Item属性以及一个用于遍历的GetEnumerator,该函数实现了IEnumerable的相应接口,还有一个用来撤销所有步骤显示的clear函数。
   

    ShowStepGroup是存储具体显示步骤的地方,所以需要添加显示步骤的add函数,以及与ShowStepCollection功能一致的Count、Item属性和GetEnumerator、clear方法。ShowStepCollection的add函数正是调用ShowStepGroup的add函数来完成添加具体步骤功能的。
    ShowStep作为抽象类具有两个抽象函数,show和clear,分别用来显示和撤销显示。

三、代码(略过
    这里的显示全部利用超图组件,因而涉及到显示的部分会传入一个大家很不熟悉的参数: map As AxSuperMapLib.AxSuperMap,这没关系,如果要是画在一个windows窗体上传入的就该是该窗体的Graphics了,当然具体到代码会有些不同。

ShowStepCollection

ShowStepGroup

ShowStep

    下面给出ShowStep的子类ShowTriangle的代码,其它子类大同小异
ShowTriangle

四、操纵显示步骤
    经过上面的三个过程,显示步骤的存储、绘制等功能已经完成,也就是说静态的结构已经搭建完毕,下面就要让这些步骤以一种合适的方式动起来。这时你有一些选择的余地,一种方式是将步骤切换、步骤组切换等逻辑分别写到ShowStepGroup以及ShowStepCollection中去,这样做之所以会被想到正是受到封装思想的影响,封装的思想是什么,自己的数据自己消费,由于各个步骤组是步骤集合ShowStepCollection的数据,所以很自然的就会想到在该类内部添加一些方法,来操纵这些组,同样的想法也会出现在步骤组中。这样的想法不好吗,这种对数据的封装不对吗,我要说的是这不涉及对错,只是在这里不太适合,有更好的方式让问题更简单。那为什么不适合呢?
    为了显示各个步骤,步骤集合ShowStepCollection需要做一些事情,ShowStepGroup也需要做一些事情,但从整体来看二者做的是同一件事情的不同部分,一个负责组切换、一个负责步骤切换,但两者之间并非泾渭分明,会有相互的影响,最明显的是当组切换时,步骤得回到新组的第一个。因而这样设计会增加二者的耦合度,并且违反了最小知识、单一职能等设计原则,扩展性极差,实现难度较高。
    我的做法是设计一个专门的类ShowStepScanner来负责控制步骤的显示,无论是组切换还是步骤切换都在这一个类里完成,将分散在ShowStepCollection和ShowStepGroup中的控制逻辑抽取出来,合在一起放到这个类的相关方法中。下面是该类的类图以及代码。
 
ShowStepScanner

    从类图和代码中可以看到,nextStep负责前进一步,通过控制组索引和步骤索引来实现下一步骤的显示,lastStep则是回退一步,animation则通过一个定时器实现了自动显示步骤的功能,如果没有将控制显示的逻辑从那两个类中提取出来,这些功能的实现是很困难的。

     一个完整的步骤显示功能实现过程展现于此,在这其中有一些我自己开发中经常用到的技巧:为有待实现功能的各参与者进行功能划分;根据参与者的特点将其形象化,在可能的情况下添加一些趣味在里面;如果某项工作的逻辑较为分散将其抽取出来组织成一个新的实体,这其实就是添加了一个参与者。经过上述过程的反复迭代,最后会形成一个结构清晰、逻辑缜密、组织良好的类结构,为后续的编码甚至维护提供有力的保障。
 posted on 2008-04-09 10:22  floodpeak  阅读(3086)  评论(8编辑  收藏  举报