原名: Collections are Cool! 作者: Doug Hennig 译文:s_tiger 资料来源:code-magazine 网址: http://www.code-magazine.com/focus/Article.aspx?quickid=0301042
集合是存贮某些事物多个实例的常用的方法。例如,树形控件具有结点的集合,微软的Word具有文档的集合。直到最近,VFP开发者想要用到集合,通常必须创建他们自己的类,而这也只能是通过对数组复杂包装来实现。然而,除了需要编写更多的代码之外,自制的集合不支持FOR EACH语句,尤其是在COM服务器中使用时更加的笨拙。Visual FoxPro 通过提供真正的集合类解决了这个问题。
集合类只有很少的几个属性、事件和方法。Add方法用来向集合中添加条目,Remove方法用来删除一个条目,Item方法返回一个条目,Count属性显示集合中共有多少条目。一个“条目”可以有不同的数值类型比如文本或数字,但通常会是一个对象。除了条目本身外,集合可以为每个条目存一个关键字,比如名称。集合中的条目可以通过两种方式被确定:
位置:Collection.Item(2)从集合中返回第2个条目 关键字:Collection.Item('Doug') 返回以"Doug" 做关键字的条目。 因为Item方法是默认的方法,如果你愿意可以忽略它;Collection.Item(2)和Collection(2) 效果完全一样。
集合可以做为数组的简单替代品。集合中的每一个条目非常类似于数组中的每一个元素。然而,因为它们是对象,所以集合比数组有更多的用途。本文将从三个特殊的用途谈一下集合的使用。
某些对象需要存贮一些事物的集合。举例来说,一个表单管理器需要知道程序中每一个打开的表单的信息。除了通过使用一个对象引用表单之外,它可能还需要知道表单中使用了哪些工具栏(这样你就可以避免对同一个工具栏的产生多个实例),表单是否加入了窗口菜单(MDI界面中用来排列窗口及拆分窗口的菜单条目,译者注),该表单实例的数目(当同一个表单打开不止一次时),等等。直到VFP8前,这些信息经常保存在数组中,每一行存一个表单,每一列存一个属性。
然而,当列的数目增加时,想知道数组中保存了哪些信息以及信息在什么地方将变得越来越困难,是第四列还是第七列保存了实例的数目?同样,因为VFP数组中不能有0行(空数组),当表单关闭,从数组中移除条目的时候,你必须特别小心。
lnForms = alen(This.aForms, 1)?1
lnCols = alen(This.aForms, 2)
if lnForms = 0
This.aForms = .NULL.
else
* lnForm 是被关闭的表单的行号
adel(This.aForms, lnForm)
dimension This.aForms[lnForms, lnCols]
endif
当你使用集合时这种复杂的情况就消失了。在集合中用一个对象代表一个表单,而数组中使用一个行来实现。该对象包含了对表单的引用,以及其它需要的信息。你更愿意写(或者读,出于以上原因)哪种代码来获得一个表单的实例呢?
* 基于数组的代码
lnPos = ascan(This.aForms, 'CustomerForm')
lnRow = asubscript(This.aForms, lnPos, 1)
lnInstance = This.aForms[lnRow, 4]
lnInstance = ;
This.oForms('CustomerForm').nInstance
从集合中移除一个对象是很easy的,因为不需要考虑数组的维度问题这里。只需要调用集合的Remove方法就够了。
设想一下,你想要调用一段程序填充某个对象中的一个数组。这不象肾结石,没有什么比传递一个成员数组更难的了。因为数组必须通过使用@操作符来引用,但你不能对一个成员数组使用@操作符,你不得不先传递到局部数组,再用ACOPY()把局部数组传递到成员数组中,然而,为了避免出错,你必须先准确定义好成员数组。在不同的程序中,我有很多代码和下面的相似:
dimension laItems[1]
SomeFunction(@laItems)
lnRows = alen(laItems, 1)
lnCols = alen(laItems, 2)
dimension This.aItems(lnRows, lnCols)
acopy(laItems, This.aItems)
使用成员集合要比成员数组好很多(假设SomeFunction函数可以正确的用到集合),这将变得和下面一样的简单:
This.oItems = createobject('Collection')
SomeFunction(This.oItems)
集合中的条目可以是任何东西,包括其它的集合。除了有些像多维数组之外,集合的集合允许你使用简单的语句访问任意位置中的对象。 假想你要处理变化的数据,用下面的代码来取得数据类型不是很nice吗?
Tables('Products').Fields('ProductID').DataType
如果Tables是一个表对象的集合,并且表对象有一个字段对象的集合,并且字段对象有一个DataType属性,这一切都将很容易做到。
附录1 中就是这样的一个例子。Tables类的Init方法中通过从CoreMeta.dbf表中读取的变化的数据,添加表和字段的集合。表中有关于表和字段的信息的列,包括cRecType 数据类型("T"表示表,"F" 表示字段),cObjectNam (表或字段的名称),以及cType (字段的数据类型)。 为了确定Customer 表中字段的数目,用下面的方法:
Tables('customer').Fields.Count
你还可以通过下的方法得到Orders.Order_Date字段的标题:
Tables('orders').Fields('order_date').Caption
VFP 集合类使得创建和使用集合中的条目变得简单。当前,数组仍然占据着它们的地位,但我估计,当VFP开发者更了解集合的时候,在大多数的应用程序中,集合将取代数组的使用。
附录1: 使用集合的集合建立变化的数据
define class Tables as Collection
procedure Init
local lcTable, loTable, lcField, loField
use CoreMeta
scan
do case
* If this is a table, add it to the collection.
case CoreMeta.cRecType = 'T'
lcTable = trim(CoreMeta.cObjectNam)
loTable = createobject('Table')
This.Add(loTable, lcTable)
* If this is a field, add it to the appropriate table.
case CoreMeta.cRecType = 'F'
lcTable = juststem(CoreMeta.cObjectNam)
lcField = trim(justext(CoreMeta.cObjectNam))
loField = createobject('Field')
with loField
.DataType = CoreMeta.cType
.Length = CoreMeta.nSize
.Decimals = CoreMeta.nDecimals
.Binary = CoreMeta.lBinary
.AllowNulls = CoreMeta.lNull
.Caption = trim(CoreMeta.cCaption)
endwith
This.Item(lcTable).Fields.Add(loField, lcField)
endcase
endscan
use in CoreMeta
endproc
enddefine
define class Table as Custom
add object Fields as Collection
enddefine
define class Field as Custom
DataType = ''
Length = 0
Decimals = 0
Binary = .F.
AllowNulls = .F.
Caption = ''
enddefine