VBA 类模块系列之十一——类代码中的警察(二)

在《系列之九——类中的常量》中,我们为类定义了枚举类型的性别常量 Public Enum pGender Fe … 继续阅读“VBA 类模块系列之十一——类代码中的警察(二)”

在《系列之九——类中的常量》中,我们为类定义了枚举类型的性别常量

Public Enum pGender
    Female = 0
    Male = 1
End Enum

然后在测试代码中为性别属性赋值的时候,IDE的智能提示跑了出来:

我当时拍着胸脯说,这回,类的使用者不会瞎给性别属性赋值了。好吧,我承认当时有点说大话了。因为当时闻到空气中兴奋的气味,所以不想扫大家的兴。

我们弄个极端的例子:某泰国的lady boy在给这个属性赋值的时候,一看提示出来的2个选项,没有一个是他(or 她?)能选的。心想,MD,这不是明摆的歧视我这个第3性嘛,我就给赋个值3,然后编译一下,居然编译通过了!

Sub test()
    Dim objperson As Person
    Set objperson = New Person
    
    objperson.Name = "赵冰冰"
    objperson.Gender = 3
    objperson.Speak
    
    MsgBox "修改前性别:" & objperson.Gender
    objperson.Gender = Male
    MsgBox "修改后性别:" & objperson.Gender
'
'以下代码省略
'
End Sub

为什么能编译通过?还记得我说过,枚举常量用起来是非常简单的,简单到什么程度?不负责任的程度^_^。枚举类型,从本质上讲,是长整形(Long),你拿任何一个长整形的值赋给它,它都无条件接受。至于你在这个枚举类型下面,定义了多少个常量,它一概不管。

好吧,既然你知道了类中枚举常量的底线轻易的就可以被人突破,导致类的属性,被设置了非法的值,你说怎么办?既然是非法,当然得交给警察来管!

我们终于切入正题了。

我们将原来性别赋值属性子程序(Public Property Let Gender)中的代码全部删掉(相信你已经掌握了如何设计只赋值一次的属性),在其中按照上一篇的样式设置警察,警察局如下面代码所示:

Public Property Let Gender(lenumGender As pGender)
    On Error GoTo ErrHandler
    If lenumGender <> Female And lenumGender <> Male Then
        '?
    Else
        menumGender = lenumGender
    End If
ExitProcedure:
    On Error Resume Next
    '退出前的善后工作
Exit Property
ErrHandler:
    '在警察局要做的事
    Resume ExitProcedure
End Property
  • 第2行,第8-14行,就是我们上一篇介绍的内容,不再赘述。
  • 第3行代码,很容易理解,如果属性被赋的值,既不是男,又不是女,那就是非法的值了!
  • 第5-7行,是合法的赋值。

现在问题就来了,第4行我们要写什么代码,才能让警察现身?上一篇是故意用一个0作为除数,本篇还要这么搞一下么?这样固然可以把警察召唤出来,但是有点为了目的,不择手段的感觉,咱们可都是有品味的人^_^。

我们这里的困境是,属性被赋了非法的值以后,怎么才能让警察知道?等等,什么是非法?这个法律是谁说了算?是你规定了性别只能是男和女,当然是你说了算。既然别人给性别赋了一个既不是男,又不是女的值,你就得跳出来说,这是非法的!而不是自己做一件非法的事(比如弄个0做为除数),以便故意把警察引出来。所以现在的问题是:你通过什么指控别人非法?

每当你穷尽脑汁还是一筹莫展的时候,就是学习新东西的时候了。话说,你对 “Err” 这个系统对象,了解么?作为一个有一定经验的VBA开发人员,你使用这个系统对象,多半都是在错误处理程序当中,当发生运行时错误了,你一般会去调用 Err.Description 来看看错误说明。这个对象还能用来指控别人非法!怎么指控?用Err对象的Raise方法(raise可以翻译理解为提起诉讼的意思)。

Err.Raise的第一个参数是错误代码(Number),可以理解为违反的是第几条法律。不要忘记了,性别必须是男或者女,这法律可是你定的,我C,有没有一种很爽的感觉?^_^,虽然这条法律是你说了算,但是你在给它编号的时候,要注意不要使用系统中已经被占用的错误编号。我们修改第4行代码如下:

Public Property Let Gender(lenumGender As pGender)
    On Error GoTo ErrHandler
    If lenumGender <> Female And lenumGender <> Male Then
        Err.Raise Number:=vbObjectError + 513, Source:="Person:Gender", Description:="性别必须为男或女"
    Else
        menumGender = lenumGender
    End If
ExitProcedure:
    On Error Resume Next
    '退出前的善后工作
Exit Property
ErrHandler:
    '在警察局要做的事
    Resume ExitProcedure
End Property

第4行代码,给错误编号为vbObjectError+513,为什么弄这么一个奇怪的编号?因为vbObjectError+512之前的号码都被占用了。vbObjectError是一个系统常量。另外,我们设置了错误的来源(Source)和错误的描述(Description)。

代码运行到第4行,被宣布违反了你制定的法律以后,警察就把你拷起来带到了警察局,第13行。

问题又来了,在警察局你要做什么事?上一篇是用MsgBox检讨了错误,就给放了。这里你要做同样的事情吗?当然,这个决定权在你。你可以MsgBox弹出来告诉用户,它违反了你的什么法律,你还能在这里将这件事记下来,写入数据库中,留下永久的案底。你想怎样都可以,因为在编程的世界里,你是上帝。^_^

对于现在的情况,我的建议是,将错误上报给上级机关,然后,然后就没有然后了。这样有什么好处吗?呃,省事,我只负责立法和司法,确保我设计的类中没有违法乱纪的行为。行政处罚就交给使用我的类对象的上级主管单位吧,是做检讨撤案,还是写入档案,由类的使用者来决定。

我们修改第13行代码如下:

Public Property Let Gender(lenumGender As pGender)
    On Error GoTo ErrHandler
    If lenumGender <> Female And lenumGender <> Male Then
        Err.Raise Number:=vbObjectError + 513, Source:="Person:Gender", Description:="性别必须为男或女"
    Else
        menumGender = lenumGender
    End If
ExitProcedure:
    On Error Resume Next
    '退出前的善后工作
Exit Property
ErrHandler:
    Err.Raise Err.Number, Err.Source, Err.Description
    Resume ExitProcedure
End Property

第13行我们还是用的Err.Raise来向调用这个子程序的程序提起违法诉讼。

现在我们整体梳理一遍这个警察故事二:首先我们设了一个警察和警察局,然后当性别被赋予违法的值的时候,我们提起了指控,代码被带到警察局,警察局直接将指控上报给了上级机关。

发现问题没有?如果警察只是把罪犯抓起来送给上级,和没有警察是一样的。因为上一篇讲过,如果你在县里不设置警察,代码在县里发生的运行时错误,会自动上报至市里的警察。所以我们可以把县里的所有警察和警察局都撤掉,代码如下:

Public Property Let Gender(lenumGender As pGender)
    If lenumGender <> Female And lenumGender <> Male Then
        Err.Raise Number:=vbObjectError + 513, Source:="Person:Gender", Description:="性别必须为男或女"
    Else
        menumGender = lenumGender
    End If
End Property

世界一下子清净了不少^_^

我们修改一下类的测试代码如下:

Sub Test()
On Error GoTo ErrHandler
    Dim objperson As Person
    Set objperson = New Person
    
    objperson.Name = "赵冰冰"
    objperson.Gender = 3
    objperson.Speak
 
    MsgBox "修改前性别:" & objperson.Gender
    objperson.Gender = Male
    MsgBox "修改后性别:" & objperson.Gender
    
'    objperson.DOB = #2/3/1995#
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
'    Debug.Print objperson.Age               '立即窗口值:23
'    objperson.DOB = #6/10/1995#
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
ExitProcedure:
    On Error Resume Next
Exit Sub
ErrHandler:
    MsgBox "错误代码:" & Err.Number & VBA.vbCrLf & _
           "错误描述:" & Err.Description & VBA.vbCrLf & _
           "错误来源: " & Err.Source, _
           vbCritical, _
           "Test 报错"
    Resume ExitProcedure
End Sub

F8逐步执行一下就会知道,虽然你将性别赋值为3,编译器没有检测出非法代码,运行的时候还是没逃出法律的制裁。

最后,按惯例,将最终全部代码贴给大家。

'模块1 测试代码
Option Compare Database
Option Explicit
 
Sub Test()
On Error GoTo ErrHandler
    Dim objperson As Person
    Set objperson = New Person
    
    objperson.Name = "赵冰冰"
    objperson.Gender = 3
    objperson.Speak
 
    MsgBox "修改前性别:" & objperson.Gender
    objperson.Gender = Male
    MsgBox "修改后性别:" & objperson.Gender
    
'    objperson.DOB = #2/3/1995#
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
'    Debug.Print objperson.Age               '立即窗口值:23
'    objperson.DOB = #6/10/1995#
'    Debug.Print objperson.DOBtoString       '立即窗口值:1995年02月03日
ExitProcedure:
    On Error Resume Next
Exit Sub
ErrHandler:
    MsgBox "错误代码:" & Err.Number & VBA.vbCrLf & _
           "错误描述:" & Err.Description & VBA.vbCrLf & _
           "错误来源: " & Err.Source, _
           vbCritical, _
           "Test 报错"
    Resume ExitProcedure
End Sub
 
Sub test2()
    Dim a As Integer
    a = 100
    Debug.Print a
End Sub
 
Sub test3()
    Dim objperson As Person
    Set objperson = New Person
    
    objperson.Name = "赵冰冰"
    objperson.Gender = Female
    objperson.Speak
End Sub
 
Sub Test4()
    Dim objPerson1 As Person
    Dim objPerson2 As Person
    
    Set objPerson1 = New Person
    Set objPerson2 = objPerson1
    Debug.Print objPerson1 Is objPerson2
    Set objPerson1 = Nothing
    Stop
    Set objPerson2 = Nothing
End Sub
'Person 类代码
Option Compare Database
Option Explicit
 
Private mstrName As String
Private menumGender As pGender
Private mstrSituation As String
Private mdatDOB As Date
 
Public Enum pGender
    Female = 0
    Male = 1
End Enum
 
Public Sub Speak()
    Select Case Situation
        Case "一般"
            MsgBox Left(Name, 1) & "小姐"
        Case "正式"
            MsgBox Left(Name, 1) & "女士"
        Case "撩"
            MsgBox "小姐姐"
        Case Else
            MsgBox Name
    End Select
End Sub
 
Private Sub Class_Initialize()
    Debug.Print "我出生了!"
    Situation = "一般"
End Sub
 
Private Sub Class_Terminate()
    Debug.Print "我挂了!"
End Sub
 
Public Property Get Name() As String
    Name = mstrName
End Property
Public Property Let Name(lstrName As String)
    If Len(lstrName) <= 4 Then
        mstrName = lstrName
    Else
        MsgBox "姓名不能超过4个字符"
    End If
End Property
 
Public Property Get Gender() As pGender
    Gender = menumGender
End Property

'版本1
'Public Property Let Gender(lenumGender As pGender)
'    Static blnFlag As Boolean
'    If blnFlag = False Then
'        blnFlag = True
'        menumGender = lenumGender
'    Else
'        MsgBox "性别一旦设定,就无法修改"
'    End If
'End Property

'版本2
'Public Property Let Gender(lenumGender As pGender)
'    On Error GoTo ErrHandler
'    If lenumGender <> Female And lenumGender <> Male Then
'        Err.Raise Number:=vbObjectError + 513, Source:="Person:Gender", Description:="性别必须为男或女"
'    Else
'        menumGender = lenumGender
'    End If
'ExitProcedure:
'    On Error Resume Next
'    '退出前的善后工作
'Exit Property
'ErrHandler:
'    Err.Raise Err.Number, Err.Source, Err.Description
'    Resume ExitProcedure
'End Property

'版本3
Public Property Let Gender(lenumGender As pGender)
    If lenumGender <> Female And lenumGender <> Male Then
        Err.Raise Number:=vbObjectError + 513, Source:="Person:Gender", Description:="性别必须为男或女"
    Else
        menumGender = lenumGender
    End If
End Property

Public Property Get Situation() As String
    Situation = mstrSituation
End Property
Public Property Let Situation(lstrSituation As String)
    mstrSituation = lstrSituation
End Property
 
Public Property Let DOB(ldatDOB As Date)
    Static blnFlag As Boolean
    If blnFlag = False Then
        blnFlag = True
        mdatDOB = ldatDOB
    End If
End Property
Public Property Get DOBtoString() As String
    DOBtoString = Format(mdatDOB, "YYYY年mm月dd日")
End Property
 
Public Property Get Age() As Integer
    Age = DateDiff("yyyy", mdatDOB, Date)
End Property

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注