同时问其他OO高手:我的类该怎样安排
我做了个树类,里面有
(1)treeview类型的成员变量m_tree,类初始化的时候指向相关联的treeview控件
(2)还有m_strTreeWndName,指向这个treeview所在的窗体名字。注意它有可能是也可能不是treeview的父窗体,由程序员根据自己的需求来定。
(3)还有m_strNodeTable,指向存储这个树控件的节点信息的节点表。
(4)还有m_strTreCtlName,指向这个treeview控件的控件名。这个直接用m_tree.name是得不到的,应为msctlcomlib.treeivew类没有name这个属性。
同一个节点表可能对应多棵树,同一个窗体上也可能有多个树。反正吧,上面4个变量唯一地规定了,当前这个类对象是哪个窗体上哪个树控件以及它是展示的哪个表的数据。
另外还有些用于树生成、增删改拖拽拷贝节点的方法。
除了一些常规的树操作,还有些我自己特别的需求,比如需要知道树节点的值和层次百分比等等,总之吧,为了方便,我给node安排了好些标志位,分别安排在node对象的text, tag, key属性里。
我的树上的节点可以多挂,就是具有同样细节的节点,既可以挂在这棵树上,也可以挂在那颗树上,只要符合一定的规则。所以,节点有多挂计数,知道自己被挂了几次。如果这个计数变为0的话,这个节点对应的细节也会被删掉。(数据库里主要是两类表,节点表和细节表。节点表存各树的节点之间的父子关系,细节表存各种细节的具体信息。节点都有细节类型,不同类型的节点的细节信息存在相应的细节表里)。
说了这么多,我是想说,我这些树它们是联动的,它们是牵一发而动全身的,因为有多挂节点的存在。
而因为存在多树联动的问题,我的树类模块就膨胀得很快,现在要从头翻到尾的都有点累手了。我自己也知道,其中有些过程和函数其实并不是这个树类特有的或直接相关的。[b]我想请教,我该如何安排这些函数?是把它们移出到标准模块里,还是另外写个什么类(可我一时又想不好能抽象出什么对象)[/b]?
举个例子吧。比如我有这么个函数,叫CopyNode。它的功能是把源树atSource上的源节点nodSource移到目标树atDest上的目标节点nodDest下。这个函数,我现在把它放在CAnnaTree类模块中(就是我在开发的这个树类)。可是其实,它并不是直接和CAnnaTree对象绑定的。没错,它是用到了atSource和atDest两个CAnnaTree对象,但是它并不是在处理CAnnaTree的内部事务,它完全是可以用来处理和它本身毫不相干的另外两棵树之间的节点拷贝。所以,我觉得它不应该放在CAnnaTree类里。我另外做个标准模块叫basTree,我把这个函数设为public放在basTree里。但说不出为什么,觉得这种方法不伦不类。呵呵。
再举个例子吧。比如我还有个函数,叫MakeNodeKey。它的功能是根据传来标志位,根据一定的规则,计算返回一个符合标准的节点key值。这个可以说也是和CAnnaTree对象本身不完全相关了,它完全可以用于其他CAnnaTree对象的节点key的计算。
如此种种,感觉很乱。
[解决办法]
数据结构是有共通性的,既然你在数据库中分为节点表和细节表,那么对象结构中至少应该有两个数据类:CAnnaTree、CAnnaDetail,关系是多对一
MakeNodeKey 应该属于 CAnnaDetail
CopyNode 根本不需要,只要 atDest.Add tvwChild, nodDest.ID, nodSource.ID
而 CAnnaTree.Add :
1)取得 CAnnaDetail.Item(ChildID),显示在 TreeView 中
2)记录父子关系 {ParentID,ChildID}
3)调用 CAnnaDetail.AddRef(ChildID)
[解决办法]
由于VB并不支持继承,想做到完全的OO是很不现实的,为了方便重用和维护也没有必要去追求绝对的OO
你所说的这个类可以做为一个框架类来实现,对于功能明确单一的函数可以做为一个公用方法。
功能不能确定或经常需要改动的,可以通过框架维护一个由定义的抽象类接口实现转发。 这样的做法,可能增加了一些复杂度, 但可以控制更灵活,更容易扩展。 在不需要修改框架的情况下直接挂接一个抽象类实现的接口仅能实现功能的修改。 而且可以有效的控制代码膨胀。
[解决办法]
或者这样实现也可以:
1 提供一个内存中的节点集合类,该类存储所有用到的节点.
2 增加一个内存中的节点类,存储引用次数,表格等信息,提供与界面同步的功能.
3 为每个节点类增加一个全局唯一的ID,在Treeview的节点的tag属性中存储对应内存节点的ID.
也许这样更合理一些:)
[解决办法]
其实你最大的问题是没有区分数据结构中集合和成员的关系!
比如对任意一个数据库表,在 .Net 中至少有两个主要数据对象 DataTable 和 DataRow。
对应到通常对象设计中,两个对象都可以实现业务规则,通常成组操作要的业务在集合类中实现,而单记录的业务规则在成员类中实现。
有时候成员如果只是简单的数据存取,利用RecordSet可以省略成员类,所有业务规则全部在集合类中实现。
这时候集合类可能不需要设计为一个集合,这时可以将它称为管理类,我前面和你讨论的就是这种情况。
如果业务负责,就应该细分设计
●对细节表
1)CAnnaDetail
Property Item(ID) As CAnnaDetailNode
2)CAnnaDetailNode
Sub AddRef()
Function IsNodeExposed() as Boolean
●对节点表
1)CAnnaTree
Public Sub DeleteChildTree(ID)
2)节点
也可以直接利用 TreeView 的 Node
其它一些业务不理解,不好定。
记住一点,用对象模型的话,CAnnaDetail 或 CAnnaTree 一次性载入一个数据表,读信息就直接在对象模型中操着,写信息就同步更改到数据库,只需要在最外层用事务保证数据一致性就可以了。
[解决办法]
3)CopyDetail2V
V 是什么?
4)GetNodeDetail3Db
oDetailList.Load(...) '如果没有载入的话
Set oDetailNode = oDetailList.Nodes(ID)
[解决办法]
关于数据库的存取:
将集合类看成 RecordSet,成员类看成 RecordSet 的 1 行。
虽然的确可以通过简单地对 RecordSet 的封装实现数据库更新,但是 RecordSet 的内存耗用通常比纯数据大1个数量级,最好用成员类保存属性。
只要在成员类中加一个私有的状态标志(你可以直接用 ADODB.RecordStatusEnum,其实只需要 New、Modified、Unmodified 三种值)
1)Tree.Load()
每个成员用 RecordSet 的数据初始化,Status = adRecUnmodified
2)Tree.Add()
新增成员的 Status = adRecNew
3)Tree.Remove()
If 删除成员.Status <> adRecNew Then
将删除成员的 ID 记录在一个内部的 DeletedNodes 列表中
End If
4)成员属性变更
If Status = adRecUnmodified Then
Status = adRecModified
End If
5)Tree.Save()
对所有 DeletedNodes 中的 ID 做 DELETE
对所有 Status = adRecModified 的成员做 UPDATE
对所有 Status = adRecnEW 的成员做 INSERT
[解决办法]
在 OO 中,每个对象都应该是“成年人”,只要给予指令或适当的通知,都能独立完成自己的工作。
事件就是常用的通知方式。
A)
'CDetailListPublic Event DetailChanged(ByVal ID As Long)Public Sub OnDetailChanged(ByVal ID) RaiseEvent DetailChanged(ID)End Sub
[解决办法]
这种问题应该不难啊,典型的观察者模式
数据部分:
从数据库里取出的要显示数据,以某种形式组织在内存里,比如可以是一个hash表。维护观察者计数和相关信息。
界面部分:
不管你是tree还是什么其他的什么ui,都是观察者,只要listen自己关心的数据就好了。
树之间复制节点什么的之类的问题,根本不应该把两者耦合起来,你管他是树还是什么其他东西呢?就是两个观察者而已,
如果以后你还要支持list,edit,static,tooltip控件,你怎么写?
从树a移走一个节点,他只用告诉数据提供者,他不关心这个数据了,他不用管是移动到哪儿去了。
移动到树b,他只要告诉说他关心这个数据了。
数据变化后,激发signal,通知关心的观察者,观察者自己去取变化了的数据即可。
[解决办法]