Re: Determine if an item in a collection has changed
- From: "Dmitriy Antonov" <antonovdima@xxxxxxxxxxxxxxxxxxxxxxxx>
- Date: Thu, 17 Nov 2005 17:48:12 -0500
"Larry Serflaten" <serflaten@xxxxxxxxxxxxxx> wrote in message
news:uXmPnp36FHA.4076@xxxxxxxxxxxxxxxxxxxxxxx
>
> "Dmitriy Antonov" <antonovdima@xxxxxxxxxxxxxxxxxxxxxxxx> wrote
>
>> >> Obviously, in PP.Save, i could enumerate each Person instance and call
>> >> the
>> >> .Save method if the instance needed persistance, but i'd rather be
>> >> smart
>> >> about it.
>> >>
>
>
>> I, probably, overlook something - how children notify parent or
>> ThirdClass
>> that their state has changed - in other words, "who" triggers all these
>> "movements"?
>
> It was evident that what he needed was a one-way request to find all those
> child objects that were dirty, possibly just before closing down.
>
> If immediate notification was required (some event called if any child
> changed) then the Third class could contain yet another event and the
> method to call it, for example, a Changed event:
>
> Public Event Changed(Item as Person)
>
> Public Sub Notify(Item As Person)
> RaiseEvent Changed(Item)
> End Sub
>
>
> Now if the both the parent and kids hold that Third class 'WithEvents',
> the parent can use Dirty to find all those kids that are dirty, while the
> kids can use Notify to inform the parent whenever they change.
>
> Obviously, both events are going to effect all the objects involved,
> they both (parent and kids) get calls for Dirty and Changed. But, the
> parent does nothing in the Dirty event and the kids do nothing in the
> Changed event. For a small group (less than 100) the performance hit
> will hardly be noticable.
>
> If the group is huge, then it would be better to use a forth class to
> facilitate the Changed event such that only the kids get the IncludeDirty
> call and only the parent gets the Changed call. Even further, both the
> third and fourth class may be encapsulated in a fifth class such that
> its the fifth class that is passed to everybody, and each object (parent
> or child) picks out the (third or fourth) object they need to hold
> 'WithEvents'.
>
> Why do it that way? Because it avoids the circular reference problem,
> and it provides a hook into the communication lines between parent and
> children such that any sort of logging, or filtering logic can be easily
> applied.
>
> LFS
>
Hi Larry,
I disagree with your first statement about what was evident. Citation: "It's
there an easy way to determine if one or more objects in a collection
has changed, without enumerating the collection ..."
In original post there was no question about how to query all children. And
your first solution effectively proposed just that. And this is why I raised
all my doubts. OP asked how to avoid querying of all of them. It can be
done only by means of notifications (in wide sense of this word - not
necessary in the form of blinking label in GUI). Each time, when collection
member has changed, it must notify some central unit about it. This central
unit should maintain a list of changed items - this list can be in different
forms: Array, Collection, etc. The best candidate for such central unit is
collection class itself.
Notification mechanism itself can be different too. Since we can't raise
events from members, because VB doesn't allow us to catch such events from
collection, we can do it using callbacks. For callbacks all children must
have a reference to callable object. If such a callable object is collection
class we have circular reference.
One way of resolving this conflict is to use the third object (we can call
it a Messenger). This is, what you propose. It is well-known schema. I can,
again, refer to Matthew Curland's book - it is described there too. In order
for this schema to become useful, children must have a reference to
Messenger and "talk to it". And it must be a reason for using this schema
(and I didn't find that reason in proposed implementation).
Messenger shouldn't have a reference to collection object - otherwise we
still have circular reference. Since messenger holds references to children
and children keep reference to Messenger, we do have circular reference
between them. But since collection class is not kept by these internal
objects, those circular references can be (and must be - it is very
important) internally resolved in collection class Terminate event -
explicitly.
I don't see a reason to employ fourth, fifth and further classes just to
organize normal work of simple collection - again, unnecessary complication
with no benefits at all (and just an opposite), while technically you can
complicate any simple schema to continue be workable, just inefficient :-)
When you use something, like recently proposed Notify method in the
Messenger, it becomes reasonable in sense of reaching OP's goal - the next
step is to organize a list of changed items (in the Messenger itself or in
the collection class) and then this list to be used instead of looping all
children. This is how I understand OP.
Again, I advocate another schema - so called weak references from children
to collection class. This allows to get rid of any intermediate classes and
concentrate on just two classes: Collection and Child. It is absolutely
workable, most efficient, maintainable and least confusing schema, IMO.
If you have hundreds of classes in your project and good half of them are
collections (because in this case we are, most likely, talking about Object
Model), then adding that additional element (Messenger or whatever)
significantly increases cost of original development and maintenance, as
well as percent of errors as well as risk of total failure. As far as I know
such dependency is not linear - increasing size and complexity of your code
by 30% will increase all those associated costs and risks by much more than
30%.
Returning to the resulting snippet that Craig built. Have you noticed that
he doesn't test how, actually, it works, if Consumer of collection (in this
case it is code in the Form) changes the Name property of any of Person.
Just adding objects to collection and saving them immediately doesn't show
us any real internal work. If he does test the code for any possible actions
that the Consumer can perform with collection and each of its members,
including retrieval, adding, deletion, changing the properties, looping and
so on, he will soon realize (as I hope) that there is something wrong in
that schema, at least in his implementation.
Here is how I would start building objects. Note - this is very schematic
snippet. It doesn't have Add, Item, Delete and many other methods and
corresponding variables. Actual implementation is very unique, depending on
functional and other requirements. For example, there should be decision how
to treat New items (not persisted yet) and Deleted items (and the situation,
when New item is deleted before saving). Whether IsChanged flag will include
New and Deleted items, or there will be separate flags and thus additional
methods or variations of proposed methods (for example, we can use Save
method to finalize persistence, but inside it we will save changed and new
items, remove Deleted (not New) items and then adjust all flags
appropriately. We need to decide how persistent items are added to
collection: it can be each children loading itself from the DB, or
collection class will be responsible for that. There are a lot of other
details, which will make particular implementation different from any other.
Since this is "air code" there can be some errors, but, as I hope, it should
be workable overall. Some procedures, such as Item property (usually set as
Default), Add method (both in Persons object) and, optionally, test
messages in Terminate events of both classes, should be added in order for
this snippet to work properly.
There is much more to say, but I'd rather propose to read documentation,
books and articles, written by people, who are much more knowledgeable then
I am :-(
Notice. While I think this conception is much much better than proposed
schema with Messenger class (regardless of actual implementation), it is
just my own opinion, which is not necessary shared by anyone else. In my
other post within this thread I noted that there are often many workable
ways to solve the same problem and, generally, none is ideal one, even
though they can significantly differ in terms of efficiency, reasonability
and other trade-offs.
<some module - can be embedded to Person class>
private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Dest As
Any, Source As Any, ByVal bytes As Long)
Public Function ObjectFromPtr(lPtr As Long) As Object
If lPtr <= 0 Then Exit Function
Dim obj As Object
CopyMemory obj, lPtr, 4&
Set ObjectFromPtr = obj
CopyMemory obj, 0&, 4&
Set obj = Nothing
End Function
</some module>
<Person>
'most likely public not creatable - exposed via Item property or Add
function of the collection
dim msName as string
dim msProp1 as string
dim msProp2 as string
dim mpParent as long
dim mflgChanged as boolean
friend property let ParentPointer(pParent as long)
mpParent=pParent
end property
private function Parent() as Persons
set Parent=ObjectFromPtr(mpParent)
end function
'at this point you can set changed flag to True only but can be done to
clear this flag, if necessary, too
friend sub ProcessChanged()
mflgChanged=true
call Parent.ManageChanged(me)
end sub
friend property get Changed() as boolean
Changed=mflgChanged
end property
public property get Name() as String
Name=msName
end property
public property let Name(NewVal as string)
if msName=NewVal then exit property
msName=NewVal
call ProcessChanged
end property
public property get Prop1() as String
Prop1=msProp1
end property
public property let Prop1(NewVal as string)
if msProp1=NewVal then exit property
msProp1=NewVal
call ProcessChanged
end property
public property get Prop2() as String
Prop2=msProp2
end property
public property let Prop2(NewVal as string)
if msProp2=NewVal then exit property
msProp2=NewVal
call ProcessChanged
end property
friend sub Save()
if not mflgChanged then exit sub
mflgChanged=False
debug.print "Saved"
end sub
friend property get ID() as long
ID=Objptr(me)'TODO - temporary solution
end property
</Person>
<Persons>
dim mcolChildren as collection
dim mcolChanged as collection
Private Sub Class_Initialize()
Debug.Print "Initialized Persons " & ObjPtr(Me)
Set mcolChildren = New Collection
Set mcolChanged = New Collection
End Sub
'optional but highly recommended
Private Sub Class_Terminate()
Dim objChild As Person
Set mcolChanged = Nothing
For Each objChild In mcolChildren
objChild.ParentPointer = 0
Next objChild
Debug.Print "Terminated Persons" & ObjPtr(Me)
End Sub
Public Function Add(Name As String) As Person
set Add=New Person
Add.ParentPointer=objptr(me)'IMPORTANT - this line should always follow
object creation
Add.Name=Name'will be added to Changed collection here
mcolChildren.Add Add,"#" & ADD.Id'notice that, when you populate not-new
items from the DB your ID schema will not work this way (ID property to be
added by OP)
end function
friend sub ManageChanged(objChild as Person)
dim pTmp as long
if objChild is nothing then exit sub
pTmp=objptr(objChild)'object's pointer is guaranteed to be unique
on error resume next
if objChild.Changed then
mcolChanged.Add objChild,"" & pTmp
else
mcolChanged.Remove "" & pTmp
end if
end sub
public property IsChanged() as boolean
ischanged=mcolChanged.count
end sub
'error handling should be added
'returns number of saved classes
public function Save() as long
dim objChild as Person
if mcolChanged.count=0 then exit function
for each objChild in mcolChanged
call objChild.Save'not that Save method of Person class is declared
as Friend - it is important here, or changes in implementation required
save=save+1'return number of saved children
next objChild
set mcolChanged=new collection'reset Changed collection
end sub
</Persons>
<The form>
'call from, e.g. some button click event
private sub DoTest
dim objPersons as Persons
dim objPerson as Person
set objPersons=new Persons
set objPerson=objPersons.Add("Craig")
objPerson.Prop1="object1"
set objPerson=objPersons.Add("Larry")
objPerson.Prop1="object2"
set objPerson=objPersons.Add("Dmitriy")
objPerson.Prop1="object3"
debug.print objPersons.IsChanged
'note - there is no loop to find changed items (neither here nor
internally)
debug.print "Saved " & objPersons.Save
debug.print objPersons.IsChanged
set objPerson=objPersons.item(1)'get the first item
objPerson.Prop1="object1 changed"
debug.pring objPersons.IsChanged
'note - there is no loop to find changed items (neither here nor
internally)
if objPersons.IsChanged then Debug.print "Saved one more time " &
objPersons.Save
debug.print objPersons.IsChanged
Debug.print "Try to save again " & objPersons.Save
set objPersons=nothing
stop'check objects destruction here
end sub
</The form>
Dmitriy.
MCSD
.
- Follow-Ups:
- Re: Determine if an item in a collection has changed
- From: Larry Serflaten
- Re: Determine if an item in a collection has changed
- References:
- Re: Determine if an item in a collection has changed
- From: Dmitriy Antonov
- Re: Determine if an item in a collection has changed
- From: Craig Buchanan
- Re: Determine if an item in a collection has changed
- From: Larry Serflaten
- Re: Determine if an item in a collection has changed
- From: Dmitriy Antonov
- Re: Determine if an item in a collection has changed
- From: Larry Serflaten
- Re: Determine if an item in a collection has changed
- Prev by Date: Kick off job on-the-fly
- Next by Date: Re: Determine if an item in a collection has changed
- Previous by thread: Re: Determine if an item in a collection has changed
- Next by thread: Re: Determine if an item in a collection has changed
- Index(es):
Relevant Pages
|