Referencing objects and program design

S

Stephan Kämper

Hi all,

suppose I have some kind of object which holds 'object ids' referring to
other objects, probably objects of its own class - or even itself.

Now adding an object to the list can be done (at least) in two ways:
Pass an object to the method and let the method find out the object id,
or pass the object id directly.

Now the following lines are just an example of what I'm thinking about
(and it's certainly not thread safe among other things).

class Common_Base
@@curr_oid = "aa"
attr_reader :eek:id

def initialize
@oid = Common_Base.get_new_oid
end

def Common_Base.get_new_oid
@@curr_oid = @@curr_oid.succ
end
end

class Reference_List < Common_Base
def initialize
super
@refs = []
end

def add_ref1( other )
@refs << other.oid unless @refs.include? other.oid
end

def add_ref2( oid )
@refs << oid unless @refs.include? oid
end
end

class Referenced < Common_Base
end

refl = Reference_List.new
ref = Referenced.new

refl.add_ref1( ref )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac"]>

refl.add_ref2( ref.oid )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac"]>

refl.add_ref1( refl )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac", "ab"]>


Now, for 'add_ref1' the method needs to know a bit more about the object
to be added (needs to call the oid method of the added method), while
'add_ref2' seems to put more work on the caller of the method (needs to
ask for the added oid).
Anyway, right now using 'add_ref1' _seems_ to be more the
'tell-the-other-guy' way, but I'm not too sure about that...
So, what's the better way to add a to-be-referenced object to the
referencing object?

Any input is welcome. :)

Happy rubying

Stephan

[1] See http://www.pragmaticprogrammer.com/articles/may_04_oo1.pdf for
more on that.
 
R

Robert Klemme

Stephan Kämper said:
Hi all,

suppose I have some kind of object which holds 'object ids' referring to
other objects, probably objects of its own class - or even itself.

Now adding an object to the list can be done (at least) in two ways:
Pass an object to the method and let the method find out the object id,
or pass the object id directly.

If there are no special requirements for your object ids I suggest to use
Object#id and ObjectSpace#_id2ref:

11:10:05 [ruby]: irb
irb(main):001:0> class Foo;end
=> nil
irb(main):002:0> f=Foo.new
=> #<Foo:0x10187fb8>
irb(main):005:0> id = f.id
=> 135020508
irb(main):006:0> ObjectSpace._id2ref id
Now the following lines are just an example of what I'm thinking about
(and it's certainly not thread safe among other things).

class Common_Base
@@curr_oid = "aa"

I'd change that to be an instance var of Common_Base:

@curr_oid = "aa"
attr_reader :eek:id

def initialize
@oid = Common_Base.get_new_oid
end

Note that, depending on whether you want to be able to resurrect instances
from some kind of persistent storage, you may need to be able to override
the oid.
def Common_Base.get_new_oid
@@curr_oid = @@curr_oid.succ
end
end

class Reference_List < Common_Base
def initialize
super
@refs = []
end

def add_ref1( other )
@refs << other.oid unless @refs.include? other.oid
end

def add_ref2( oid )
@refs << oid unless @refs.include? oid

You could better use a Set here. That saves you the inefficient check
(with an Array this is O(n) where n is the number of oids stored so far;
with a Set (hash internally) it's ~ O(1)).
end
end

class Referenced < Common_Base
end

refl = Reference_List.new
ref = Referenced.new

refl.add_ref1( ref )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac"]>

refl.add_ref2( ref.oid )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac"]>

refl.add_ref1( refl )
p refl # -> <Reference_List:0x27862b0 @oid="ab", @refs=["ac", "ab"]>


Now, for 'add_ref1' the method needs to know a bit more about the object
to be added (needs to call the oid method of the added method), while
'add_ref2' seems to put more work on the caller of the method (needs to
ask for the added oid).
Anyway, right now using 'add_ref1' _seems_ to be more the
'tell-the-other-guy' way, but I'm not too sure about that...
So, what's the better way to add a to-be-referenced object to the
referencing object?

I'd go for add_ref1 - although you should uncover a bit more of the
problem you are solving.

Regards

robert
 
S

Stephan Kämper

Hi,

Thanks for that answer (this goes to Assaph too, of course).

Robert said:
If there are no special requirements for your object ids I suggest to use
Object#id and ObjectSpace#_id2ref:

I'll add that to my notes and think about it.
You could better use a Set here. That saves you the inefficient check
(with an Array this is O(n) where n is the number of oids stored so far;
with a Set (hash internally) it's ~ O(1)).

Thanks for the tip - I'm currently still in the second of the three
phases (Make it run, make it right, make it fast)... :)

I'd go for add_ref1 - although you should uncover a bit more of the
problem you are solving.

Yes, in the meantime I've come to that conclusion, too.
Having worked for an object-oriented database vendor some time ago,
things like traversing object networks, persistence and similar themes
come to my mind now and then...

Happy rubying

Stephan
 
R

Robert Klemme

Claus Spitzer said:
Traversing object networks... that seems like an application for
ObjectSpace.each_object

No, he rather means, doing a graph search on a set of connected objects.
ObjectSpace.each_object won't help you there, because that yields *all*
objects. Typically you're only interested in a certain sub set, i.e. all
objects that were retrieved from some persistent store within a single
transaction (so you can write them all back) etc.

Regards

robert
 
R

Robert Klemme

Stephan Kämper said:
Hi,

Thanks for that answer (this goes to Assaph too, of course).



I'll add that to my notes and think about it.

I'm still wondering what you're at...
Thanks for the tip - I'm currently still in the second of the three
phases (Make it run, make it right, make it fast)... :)

Well, in that case I'd say performance is a nice add on but the real
benefit is that you use the appropriate abstraction: you really want a
set, so use a set.
Yes, in the meantime I've come to that conclusion, too.
Having worked for an object-oriented database vendor some time ago,
things like traversing object networks, persistence and similar themes
come to my mind now and then...
:))

Happy rubying

Always! :)

Regards

robert
 
R

Robert Klemme

Claus Spitzer said:
I see your point. While ObjectSpace.each_object can be limited to
objects of a certain class or module, it doesn't help when neither is
certain. On the other hand, would it be possible to extend ObjectSpace
to keep such lists? I'm just going with the reuse, reduce, recycle
principle

That's the wrong place IMHO. You want application dependend traversal for
which there is no general mechanism. Better put it into application code.

Btw, Marshal and YAML do already such a thing (object graph traversal).
Maybe there is some code that you can reuse. Other than that, it can be
as simple as:

def traverse(obj)
seen = {}
queue = [obj]
enq = lambda {|x| queue.push x unless seen[x.id]}

until queue.empty?
o = queue.shift
seen[o.id]=true
yield o

case o
when Hash
o.each {|k,v| enq[ k ]; enq[ v ]}
when Enumerable
o.each {|e| enq[ e ] }
else
o.respond_to? :instance_variables_get and
o.instance_variables_get.each do |iv|
enq[ o.instance_variable_get( iv ) ]
end
end
end
end

traverse( 1=>2, 3=>4 ) do |o|
p o
end

Note: there might be some additional cases I didn't take care of.

Regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,149
Latest member
Vinay Kumar Nevatia0
Top