第三回:实现步骤显示,一步一步看得见


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

一、设计类结构
    在设计本文主题——步骤显示功能的结构时,正是运用了这种形象化的设计方式。首先在一张空白的纸上我画上了一个管理所有显示步骤的集合类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
 1Public Class ShowStepCollectionClass ShowStepCollection
 2    Implements IEnumerable(Of ShowStepGroup)
 3
 4    Private myStepGroups As New List(Of ShowStepGroup)
 5
 6    Public Sub addGourp()Sub addGourp()
 7        Me.myStepGroups.Add(New ShowStepGroup)
 8    End Sub

 9
10    Public Sub add()Sub add(ByVal ss As ShowStep)
11        Me.myStepGroups(Me.myStepGroups.Count - 1).add(ss)
12    End Sub

13
14    Public ReadOnly Property Count()Property Count() As Integer
15        Get
16            Return Me.myStepGroups.Count
17        End Get
18    End Property

19
20    Default Public ReadOnly Property Item()Property Item(ByVal i) As ShowStepGroup
21        Get
22            Return Me.myStepGroups(i)
23        End Get
24    End Property

25
26    Public Sub clear()Sub clear(ByRef map As AxSuperMapLib.AxSuperMap)
27        For Each stepGroups As ShowStepGroup In Me.myStepGroups
28            stepGroups.clear(map)
29        Next
30    End Sub

31
32    Public Function GetEnumerator()Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of ShowStepGroup) Implements System.Collections.Generic.IEnumerable(Of ShowStepGroup).GetEnumerator
33        Return Me.myStepGroups.GetEnumerator
34    End Function

35
36    Public Function GetEnumerator1()Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
37        Return Me.myStepGroups.GetEnumerator
38    End Function

39
40End Class

41

ShowStepGroup
 1Public Class ShowStepGroupClass ShowStepGroup
 2    Implements IEnumerable(Of ShowStep)
 3
 4    Private myStepList As New List(Of ShowStep)
 5
 6    Public Sub add()Sub add(ByVal ss As ShowStep)
 7        Me.myStepList.Add(ss)
 8    End Sub

 9
10    Public ReadOnly Property Count()Property Count() As Integer
11        Get
12            Return Me.myStepList.Count
13        End Get
14    End Property

15
16    Default Public ReadOnly Property Item()Property Item(ByVal i) As ShowStep
17        Get
18            Return Me.myStepList(i)
19        End Get
20    End Property

21
22    Public Sub clear()Sub clear(ByRef map As AxSuperMapLib.AxSuperMap)
23        For Each showstep As ShowStep In Me.myStepList
24            If (TypeOf showstep Is IStableStep) Then Continue For
25            showstep.clear(map)
26        Next
27    End Sub

28
29    Public Function GetEnumerator()Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of ShowStep) Implements System.Collections.Generic.IEnumerable(Of ShowStep).GetEnumerator
30        Return Me.myStepList.GetEnumerator
31    End Function

32
33    Public Function GetEnumerator1()Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
34        Return Me.myStepList.GetEnumerator
35    End Function

36
37End Class

38

ShowStep
1Public MustInherit Class ShowStepClass ShowStep
2
3    Public MustOverride Sub show()Sub show(ByRef map As AxSuperMapLib.AxSuperMap)
4    Public MustOverride Sub clear()Sub clear(ByRef map As AxSuperMapLib.AxSuperMap)
5
6End Class

7

    下面给出ShowStep的子类 ShowTriangle的代码,其它子类大同小异
ShowTriangle
 1Imports SuperMapLib
 2
 3Public Class ShowTriangleClass ShowTriangle
 4    Inherits ShowStep
 5
 6    Protected myStyle As soStyle
 7    Private point1 As Point2
 8    Private point2 As Point2
 9    Private point3 As Point2
10
11    Public Sub New()Sub New(ByVal point1 As Point2, ByVal point2 As Point2, ByVal point3 As Point2)
12        Me.point1 = point1
13        Me.point2 = point2
14        Me.point3 = point3
15        Me.myStyle = New soStyle
16        myStyle.PenColor = System.Convert.ToUInt32(RGB(25500)) '边界的颜色设置为红色
17        myStyle.PenWidth = 8           '宽度为8
18        myStyle.BrushColor = System.Convert.ToUInt32(RGB(25500))   '填充区域颜色设置为红色
19        myStyle.BrushOpaqueRate = 30   '透明度设置为30
20    End Sub

21
22    Public Overrides Sub show()Sub show(ByRef map As AxSuperMapLib.AxSuperMap)
23        Dim triangle As New soGeoRegion
24        Dim points As New soPoints
25        points.Add2(Me.point1.X, Me.point1.Y)
26        points.Add2(Me.point2.X, Me.point2.Y)
27        points.Add2(Me.point3.X, Me.point3.Y)
28        points.Add2(Me.point1.X, Me.point1.Y)
29        triangle.AddPart(points)
30        map.TrackingLayer.RemoveEvent("step:triangle" & Me.point1.ToString & Me.point2.ToString & Me.point3.ToString)
31        map.TrackingLayer.AddEvent(triangle, myStyle, "step:triangle" & Me.point1.ToString & Me.point2.ToString & Me.point3.ToString)
32        map.TrackingLayer.Refresh()
33    End Sub

34
35    Public Overrides Sub clear()Sub clear(ByRef map As AxSuperMapLib.AxSuperMap)
36        map.TrackingLayer.RemoveEvent("step:triangle" & Me.point1.ToString & Me.point2.ToString & Me.point3.ToString)
37        map.TrackingLayer.Refresh()
38    End Sub

39
40End Class

41

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

 15
 16    Public Sub nextStep()Sub nextStep(ByRef map As AxSuperMapLib.AxSuperMap)
 17        If (currentStepIndex = stepGroups(Me.currentGroupIndex).Count - 1Then  '如果达到某组末尾
 18            For Each showstep As ShowStep In Me.stepGroups(currentGroupIndex)   '将本组内的所有步骤清掉
 19                If (TypeOf showstep Is IStableStep) Then Continue For '如果是稳定步骤不予处理
 20                showstep.clear(map)
 21            Next
 22            If (currentGroupIndex = stepGroups.Count - 1Then  '如果达到组集合的末尾
 23                currentGroupIndex = 0                           '返回组集合头
 24                Dim i As Integer = 1
 25                Do While i <> map.TrackingLayer.EventCount + 1
 26                    If (map.TrackingLayer.Event(i).Tag.StartsWith("step:")) Then
 27                        map.TrackingLayer.RemoveEvent(i)
 28                    Else
 29                        i += 1
 30                    End If
 31                Loop
 32            Else                            '否则
 33                currentGroupIndex += 1      '进入下一组
 34            End If
 35            currentStepIndex = 0            '回到下一组头
 36        Else                                '如果没有达到组尾
 37            currentStepIndex += 1           '前进到下一步
 38        End If
 39        If (Me.currentStepIndex > 0Then   '如果不是第一个步骤
 40            Dim lastStep As ShowStep = Me.stepGroups(Me.currentGroupIndex)(Me.currentStepIndex - 1'得到上一个步骤
 41            If (TypeOf lastStep Is ITempStep) Then      '如果上一步是临时步骤
 42                lastStep.clear(map)                     '清掉上一步骤
 43            End If
 44        End If
 45        Dim currentStep As ShowStep = Me.stepGroups(Me.currentGroupIndex)(Me.currentStepIndex)  '得到当前步骤
 46        currentStep.show(map)       '显示当前步骤
 47    End Sub

 48
 49    Public Sub lastStep()Sub lastStep(ByRef map As AxSuperMapLib.AxSuperMap)
 50        If (currentStepIndex <= 0Then  '如果达到某组头(0)或第一次扫描步骤(-1)
 51            If (currentGroupIndex = 0Then  '如果达到组集合头
 52                currentStepIndex = -1                           '返回组集合头
 53                Do While Not (currentGroupIndex = stepGroups.Count - 1 AndAlso currentStepIndex = stepGroups(stepGroups.Count - 1).Count - 1)
 54                    Me.nextStep(map)
 55                Loop
 56                currentGroupIndex = stepGroups.Count - 1                           '返回组集合尾
 57                currentStepIndex = 0                           '返回组集合头
 58            Else                            '否则
 59                For Each showstep As ShowStep In Me.stepGroups(currentGroupIndex)   '将本组内的所有步骤清掉
 60                    showstep.clear(map)
 61                Next
 62                currentGroupIndex -= 1      '进入前一组
 63                currentStepIndex = 0            '回到下一组头
 64                Do While currentStepIndex <> Me.stepGroups(currentGroupIndex).Count - 1
 65                    Me.nextStep(map)
 66                Loop
 67                currentStepIndex = Me.stepGroups(Me.currentGroupIndex).Count - 1            '回到下一组尾
 68            End If
 69        Else                                '如果没有达到组尾
 70            currentStepIndex -= 1           '前进到下一步
 71        End If
 72        If (Me.currentStepIndex < Me.stepGroups(Me.currentGroupIndex).Count - 1Then   '如果不是最后一个步骤
 73            Dim nextStep As ShowStep = Me.stepGroups(Me.currentGroupIndex)(Me.currentStepIndex + 1'得到下一个步骤
 74            If Not (TypeOf nextStep Is IStableStep) Then      '如果下一步是临时步骤
 75                nextStep.clear(map)                     '清掉下一步骤
 76            End If
 77        End If
 78        Dim currentStep As ShowStep = Me.stepGroups(Me.currentGroupIndex)(Me.currentStepIndex)  '得到当前步骤
 79        currentStep.show(map)       '显示当前步骤
 80    End Sub

 81
 82    Private supermap As AxSuperMapLib.AxSuperMap
 83
 84    Public Sub animation()Sub animation(ByRef map As AxSuperMapLib.AxSuperMap)
 85        If (timer Is NothingThen
 86            timer = New Timer()
 87        End If
 88        If (timer.Enabled) Then
 89            timer.Stop()
 90        Else
 91            Me.supermap = map
 92            timer.Interval = 500
 93            timer.Start()
 94        End If
 95    End Sub

 96
 97    Private Sub timer_Tick()Sub timer_Tick(ByVal sender As ObjectByVal e As System.EventArgs) Handles timer.Tick
 98        Me.nextStep(supermap)
 99    End Sub

100
101    Public Sub Dispose()Sub Dispose()
102        If (timer IsNot Nothing AndAlso timer.Enabled) Then
103            timer.Stop()
104        End If
105    End Sub

106
107End Class

108

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

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

转载于:https://www.cnblogs.com/floodpeak/archive/2008/04/09/1144004.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值