在36. 面向对象的LotusScript(八)之继承里,我们提到LotusScript中继承的应用可以分为两类。第一类符合面向对象思想中继承的原意,已经在导出Excel的系列文章里例示了。第二类特殊应用,则需要先回顾一下33. 面向对象的LotusScript(六)之为自定义对象模拟事件。
在笔者的这个关于Notes的系列文章中,介绍的代码使用起来都很方便,唯一的例外就是在LotusScript里模拟事件机制的代码。在那篇文章的结尾,也说明了因为LotusScript本身的限制,模拟的事件机制无法像在C#, JavaScript等语言中那样强大和完善,使用起来也有诸多不便。最关键的原因就是,LotusScript里函数不能作为变量传递和调用,而要用Execute语句解释输入的字符串来模拟。而这个Execute又和JavaScript里的Eval语句不同,有很大限制:
Execute语句执行的代码不能访问调用它的函数的变量和函数所在的类的字段这些环境信息;只能访问调用它的函数所在的模块的公共变量以及该模块引用的其他脚本库的公共变量;如果Execute语句接受的代码字符串开头就引用了某个脚本库,其中的公共变量也能被访问,而且有意思的是模拟事件所应用的情况:程序从脚本库LibA开始运行,LibA引用脚本库LibB,并将模拟函数指针的{Use LibA :Call FunctionC()}传入脚本库LibB中定义的对象ObjD,ObjD在用Execute语句执行上述代码时,FunctionC()能够访问脚本库LibA里的公共变量,并且访问的是和最初开始运行的LibA里的代码读写到的相同的变量实例。
上面这段话理解起来有些难度,要在应用事件机制的代码时,一直把它们记在心中,更是令人郁闷。所以笔者也发现,需要用更好的方法来代替它。这个方法就是继承。
我们先来假想一个例子,在一门面向对象的语言如C#中,用户需要在表单上单击一个按钮来完成某个功能。一般来说,我们会先在表单上添加一个按钮控件,然后为它增加单击事件响应程序。但是,我们也可以有另一种选择。设想按钮类实现了一个事件接口,在按钮上发生某个用户输入事件时,比如鼠标单击,接口里对应事件的响应函数就会被调用,不过按钮类作为基类,每个响应方法都是空的。需要时,创建一个子类继承自按钮,在这个子类里,重载要响应的事件对应的方法。我们通常都会选择前一种方案,因为在这些语言中,实现事件机制是很自然方便的。但是遇到了LotusScript的限制,选择后者反而变得相对容易。
我们用继承的方式改写在35. 面向对象的LotusScript(七)之导入Excel里的ExcelImporter类,变化就在ImportLine()方法的开始和结束处以及新增的QuerySaveDoc()和QueryCreateDoc()方法。
'Import one line of data. Private Function ImportLine() 'Use derived class to implement events instead of 'Call OnEvent("QuerySaveDoc") If Not me.QueryCreateDoc() Then Exit Function Set doc = db.CreateDocument doc.Form = Me.docForm Dim Field As String, value As Variant Dim i As Integer For i = 1 To colNum 'value=xlSheet.Cells(rowNum,i).Value Call doc.ReplaceItemValue( xlSheet.Cells(2, i).Value, xlSheet.Cells(rowNum,i).Value ) Next 'Mark all the document with the preset time. doc.ImportTime = ImportTime If Not me.QuerySaveDoc() Then Exit Function Call doc.ComputeWithForm( False, False ) Call doc.Save( True, False ) End Function %REM Called before a document is saved with a line of data from the Excel file. To be extended in child classes. %END REM Private Function QuerySaveDoc() As Boolean QuerySaveDoc=True End Function %REM Called before a document is created. To be extended in child classes. %END REM Private Function QueryCreateDoc() As Boolean QueryCreateDoc=True End Function
这样我们实际的导入就由下面的子类完成,它就是将35. 面向对象的LotusScript(七)之导入Excel里导入的函数移到了一个类中。
Private Class RecordImporter As ExcelImporter Private Function QuerySaveDoc() As Boolean Dim sheet As Variant Set sheet=importer.GetSheet() Dim rowNum As Integer rowNum=importer.GetRowNum() Dim doc As NotesDocument 'current imported document Set doc=importer.GetCurrentDoc() 'from code to desc Dim account As String, desc As Variant account=sheet.Cells(rowNum, 4).Value Call doc.Replaceitemvalue("Account", CStr(account)) desc=DBLookUp("vwAccount", account, 1, "") If Not IsEmpty(desc) Then Call doc.Replaceitemvalue("Description", desc) End If QuerySaveDoc=True End Function End Class
类似地,实现流程功能的SimpleFlow类里在提交前和提交后模拟的事件也可以用继承的方法来改写。