Refernce objects

R

Richard Turner

Hi,

I'm new to Ruby, so I'm still learning the Ruby Way. I'm also reading
Martin Fowler's 'Refactoring' at the moment and have realised that some
of the classes I've created in a program I'm writing fit his description
of value objects that should be refactored into reference objects.
Those classes are, in fact, wrappers over entities in a DB so I need a
factory (creation) method to always return the same object when given
the same creation parameter. E.g.:

Section.getSection(10) always returns the same object representing the
record in the DB with primary key '10'.

Since I can't make Section's constructor private in Ruby I wonder how
should I refactor my class into a reference object class? Indeed,
should I do this or does the Ruby Way use some other method?

Cheers,

Richard.
 
D

David A. Black

Hi --

Hi,

I'm new to Ruby
Welcome!

, so I'm still learning the Ruby Way. I'm also reading
Martin Fowler's 'Refactoring' at the moment and have realised that some
of the classes I've created in a program I'm writing fit his description
of value objects that should be refactored into reference objects.
Those classes are, in fact, wrappers over entities in a DB so I need a
factory (creation) method to always return the same object when given
the same creation parameter. E.g.:

Section.getSection(10) always returns the same object representing the
record in the DB with primary key '10'.

Since I can't make Section's constructor private in Ruby I wonder how
should I refactor my class into a reference object class? Indeed,
should I do this or does the Ruby Way use some other method?

At the risk of over-simplifying, let me try to simplify :)

Generally in Ruby you don't need or see too many methods with 'get'
and 'set' in their names, since there are a variety of ways to perform
those operations less verbosely. For example, you can define a method
called [] and then use index-style bracket syntax:

class Section
def self.[](n)
# here, a fetch command for record n
end
end

record = Section[10]

That would bypass the need for a constructor (if that's appropriate in
your case).


David
 
R

Richard Turner


Thank you :)
At the risk of over-simplifying, let me try to simplify :)

Generally in Ruby you don't need or see too many methods with 'get'
and 'set' in their names, since there are a variety of ways to perform
those operations less verbosely. For example, you can define a method
called [] and then use index-style bracket syntax:

class Section
def self.[](n)
# here, a fetch command for record n
end
end

record = Section[10]

That would bypass the need for a constructor (if that's appropriate in
your case).

That's great, but isn't that just a more succinct way of doing the same
thing? I.e. isn't this the same?:

class Section
def self.loadedFromDB(sectionID)
# here, a fetch command for record sectionID
end
end

What I want []() or loadedFromDB() to return is a Section, but I don't
want people to be able to call Section.new in error, otherwise they'll
not get what they think. E.g.:

secFromFactory1 = Section.getSection(10)
=> #<Section:0xb7f8d468 @index=1, @firstQuestionID=515, @name="Living
Arrangements", @sectionID=10>
secFromFactory2 = SectionFactory.getSection(10)
=> #<Section:0xb7f8d468 @index=1, @firstQuestionID=515, @name="Living
Arrangements", @sectionID=10>

BUT:
secFromNew = Section.new(10)
=> #<Section:0xb7f728e8 @index=1, @firstQuestionID=515, @name="Living
Arrangements", @sectionID=10>

See the object IDs are the same from the factory but different using the
constructor. So if I do:

secFromFactory1.index = 2
secFromFactory2.index
=> 2
BUT:
secFromNew.index
=> 1

In short, I've rather painfully created a factory method that, given an
ID, returns the correct object from a pre-filled set. However, I now
need to stop people using Section.new and I can't. This leads me to
believe that there may be a Better Way, but I don't know what it is :(

Cheers,

Richard.
 
J

James Edward Gray II

class Section
def self.[](n)
# here, a fetch command for record n
end
end

record = Section[10]

That would bypass the need for a constructor (if that's appropriate in
your case).

That's great, but isn't that just a more succinct way of doing the same
thing? I.e. isn't this the same?:

class Section
def self.loadedFromDB(sectionID)
# here, a fetch command for record sectionID
end
end

Yes, but less Javaish and more Rubyish. ;) We're not a wordy bunch
and [] is our standard accessor, say you convey the same information,
but type less.
What I want []() or loadedFromDB() to return is a Section, but I don't
want people to be able to call Section.new in error, otherwise they'll
not get what they think. E.g.:

class Selection
private_class_method :new

def self.[]( id )
Section.new(
# whatever...
)
end
end

That help?

James Edward Gray II
 
G

Guest

Richard said:
In short, I've rather painfully created a factory method that, given an
ID, returns the correct object from a pre-filled set. However, I now
need to stop people using Section.new and I can't. This leads me to
believe that there may be a Better Way, but I don't know what it is :(

There are several things you can do (I think).

First you can simply make the initialize or self.new methods private.
Secondly you can override the self.new method to be the factory method
instead of [] or both. And third, there probably exists a Factory module
of some kind which does everything I just told you for you when you
simply include the module in your class.

Regards,

Peter
 
R

Richard Turner

Yes, but less Javaish and more Rubyish. ;) We're not a wordy bunch
and [] is our standard accessor, say you convey the same information,
but type less.
Fair enough :) I'd shy away from it because to me (at least, at the
moment), it seems to imply enumeration. I'd be tempted to assume that
Section[] would 'fill' from index 0 so that, if there are any Sections,
there will certainly be Section[0]. Since the parameter is an ID, not
an index, it doesn't seem right.

On the other hand, Section{} sits fine in my head, since there's no
implied order there :)
class Selection
private_class_method :new

def self.[]( id )
Section.new(
# whatever...
)
end
end

That help?

James Edward Gray II

Perfect! Thanks. I'd just stumbled onto the idea of

class Section
class <<self
private :new
end
 
F

Florian Gross

Richard said:
Hi,
Moin.

I'm new to Ruby, so I'm still learning the Ruby Way. I'm also reading
Martin Fowler's 'Refactoring' at the moment and have realised that some
of the classes I've created in a program I'm writing fit his description
of value objects that should be refactored into reference objects.
Those classes are, in fact, wrappers over entities in a DB so I need a
factory (creation) method to always return the same object when given
the same creation parameter. E.g.:

Section.getSection(10) always returns the same object representing the
record in the DB with primary key '10'.

I think this is also called the Multiton pattern.

Something like this ought to work: (I'm reusing .new here, I think it
would be a bigger surprise to have .new raise an Exception than it not
returning an unique object every time. You can however still provide
your own constructor and make it private fairly easily with class <<
self; private :new; end)

require 'thread'
class MyMultiton
# I'm not sure if this Mutex is really needed. The Hash class might
# already be using a critical section around allocator block calls
# anyway. Feedback on this is welcome.
@instance_cache_mutex = Mutex.new
@instance_cache = Hash.new do |hash, args|
result = self.allocate
result.send:)initialize, *args)
hash[args] = result
end

def self.new(*args)
@instance_cache_mutex.synchronize do
@instance_cache[args]
end
end

# Or whatever your initialize looks like...
def initialize(value)
@value = value
end
end

It can probably be done in a simpler way, but this ought to work.
 
J

James Edward Gray II

Yes, but less Javaish and more Rubyish. ;) We're not a wordy bunch
and [] is our standard accessor, say you convey the same information,
but type less.
Fair enough :) I'd shy away from it because to me (at least, at the
moment), it seems to imply enumeration. I'd be tempted to assume that
Section[] would 'fill' from index 0 so that, if there are any Sections,
there will certainly be Section[0]. Since the parameter is an ID, not
an index, it doesn't seem right.

On the other hand, Section{} sits fine in my head, since there's no
implied order there :)

We use [] for Arrays and Hashes:

names = { :james => "Gray" } # define a Hash
names[:james] # access a Hash entry

Hope that clears up the choice.

James Edward Gray II
 
F

Florian Gross

Richard said:
Fair enough :) I'd shy away from it because to me (at least, at the
moment), it seems to imply enumeration. I'd be tempted to assume that
Section[] would 'fill' from index 0 so that, if there are any Sections,
there will certainly be Section[0]. Since the parameter is an ID, not
an index, it doesn't seem right.

On the other hand, Section{} sits fine in my head, since there's no
implied order there :)

But we use the #[] method for both Hash, Array and Function access:

hash = { 1 => 2 }
hash[1]

ary = [1, 2]
ary[1]

fun = lambda { |x| x * 2 }
fun[2]

# mixture between lambda and hash. Nifty way for doing cached lambdas.
factorial = Hash.new do |hash, key|
hash[key] = case
when key == 1: 1
when key < 1: 0
else hash[key - 1] * key
end
end
factorial[500]
 
R

Richard Turner

We use [] for Arrays and Hashes:

names = { :james => "Gray" } # define a Hash
names[:james] # access a Hash entry

Hope that clears up the choice.

I think my brain's been melting today! I knew that, but somehow forgot
it until I tries to def Section.{}! :(

Thanks, my Section class is now much smaller and well factored. You
help is much appreciated!

Richard.
 
A

Anders Engström

Yes, but less Javaish and more Rubyish. ;) We're not a wordy bunch
and [] is our standard accessor, say you convey the same information,
but type less.
Fair enough :) I'd shy away from it because to me (at least, at the
moment), it seems to imply enumeration. I'd be tempted to assume that
Section[] would 'fill' from index 0 so that, if there are any Sections,
there will certainly be Section[0]. Since the parameter is an ID, not
an index, it doesn't seem right.

I agree :)
On the other hand, Section{} sits fine in my head, since there's no
implied order there :)
class Selection
private_class_method :new

def self.[]( id )
Section.new(
# whatever...
)
end
end

That help?

James Edward Gray II

Perfect! Thanks. I'd just stumbled onto the idea of

class Section
class <<self
private :new
end
.
.
.
end

Another option is to use the :new method on the class to implement the
class Section

class << self
# Called to get (or create) new instance
def new(id)
cached_instances()[id] || cached_instances()[id] = super
end

def cached_instances()
@cache = {} unless @cache
@cache
end
end


# Called on new instance
def initialize(id)
puts "Creating a new instance"
@id = id
end

end

puts Section.new(10).inspect
puts Section.new(11).inspect
puts Section.new(10).inspect
puts Section.new(11).inspect

Output:

Creating a new instance
#<Section:0xb7eeb6c4 @id=10>
Creating a new instance
#<Section:0xb7eeb64c @id=11>
#<Section:0xb7eeb6c4 @id=10>
#<Section:0xb7eeb64c @id=11>

This way you hide the factory pattern behind a "normal" constructor,
which might be nice (I wish java had something like this without
resorting to byte-code manipulation).

//Anders
 
J

Jacob Fugal

Another option is to use the :new method on the class to implement the
class Section

class << self
# Called to get (or create) new instance
def new(id)
cached_instances()[id] || cached_instances()[id] = super
end

def cached_instances()
@cache = {} unless @cache
@cache
end
end

# Called on new instance
def initialize(id)
puts "Creating a new instance"
@id = id
end

end

Equivalent, but somewhat more concise and rubyish (IMO):

class Section
class << self
# Called to get (or create) new instance
def new(id)
cached_instances[id] ||= super
end

def cached_instances
@cache ||= {}
end
end

# Called on new instance
def initialize(id)
puts "Creating a new instance"
@id = id
end
end

Changes:
1) The ||= operation is very useful for defaults. The statement

a ||= b

simply expands to

a = a || b

which is equivalent to the

a || a = b

that you had written. It is also equivalent to the

a = b unless a

which you had in the second function.

2) An empty parameter list is usually unnecessary, thus you can both
define and call cached_instances without needing the '()'. Attaching
an empty parameter list differs from no parameter list in only two
cases I can think of; a) super vs. super() and b) disambiguating
between a local variable and method of the same name (this may not
even be an issue).

3) The return value of a ||= b is the new value of a (either what it
was, or b if it didn't have a previous value), so the explicit mention
of @cached as the return value is unnecessary in cached_instances.

Jacob Fugal
 
D

David A. Black

Hi --

2) An empty parameter list is usually unnecessary, thus you can both
define and call cached_instances without needing the '()'. Attaching
an empty parameter list differs from no parameter list in only two
cases I can think of; a) super vs. super() and b) disambiguating
between a local variable and method of the same name (this may not
even be an issue).

You can use them that way:

irb(main):003:0> def x; 1; end; x = 2
=> 2
irb(main):004:0> x
=> 2
irb(main):005:0> x()
=> 1

though just making the receiver explicit (self) is fine too. Other
than those cases I can't think of any time you'd need empty parens.
Everything after the dot is automatically a method call, so the parens
don't convey any new information.


David
 
J

Jacob Fugal

You can use them that way:

irb(main):003:0> def x; 1; end; x = 2
=> 2
irb(main):004:0> x
=> 2
irb(main):005:0> x()
=> 1

though just making the receiver explicit (self) is fine too.

Good to know. I saw the possibility in my head but had never
experienced a variable/method namespace conflict before so I didn't
know what the default handling was, which is why I added the
disclaimer. I'll remember which is right, now.

Jacob
 

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
474,164
Messages
2,570,898
Members
47,440
Latest member
YoungBorel

Latest Threads

Top