About using blocks in constructors in general…
(Although “initialize” is an initializer and not a constructor.
It only takes care of one part of the construction.)
“initialize” should take care of the initialization of the
object: Setting instance variables and checking whether the
instance variables are set correctly. It’s the latter which is
important.
Setting instance variables is traditionally done in two ways: a
list of arguments and a hash of options. (Or a combination of
them.) You might want to call them “arguments” and “named
arguments”.
The list:
def initialize(first_name, last_name)
@first_name = first_name.to_s
@last_name = last_name.to_s
raise ArgumentError, “first name isn’t set” unless @first_name
raise ArgumentError, “last name isn’t set” unless @last_name
end
The hash:
def initialize(options={})
@case_sensitive = options[:case_sensitive] || true
@direction = options[:direction] || :forward
raise ArgumentError, “” unless @first_name
end
In Java, you’ll often see a third way (Java style, Ruby code):
attr_writer :first_name
attr_writer :last_name
def initialize
end
something = Something.new
something.first_name = “Erik”
something.last_name = “Veenstra”
Personally, I think this is conceptually unacceptable: There’s
no way for the constructor to check whether the instance
variables are set, or whether they are set correctly.
This can be overcome by calling the initializer with a block:
attr_writer :first_name
attr_writer :last_name
def initialize(&block)
block.call(self)
raise ArgumentError, “first name isn’t set” unless @first_name
raise ArgumentError, “last name isn’t set” unless @last_name
end
Which is called like this:
Something.new do |something|
something.first_name = “Erik”
something.last_name = “Veenstra”
end
Personally, I always try to freeze the object once it is
created: (This has advantages beyond the scope of this writing.)
attr_writer :first_name
attr_writer :last_name
def initialize(&block)
block.call(self)
raise ArgumentError, “first name isn’t set” unless @first_name
raise ArgumentError, “last name isn’t set” unless @last_name
freeze
end
There’s some more advantages. For example, the object can often
be collected earlier. Consider this example, in the main of the
application:
direction = :forward
EV::CommandLine.new do |cl|
cl.option(“-r”, “–reverse”) {direction = :backward}
end.parse(ARGV)
…instead of…
direction = :forward
cl = EV::CommandLine.new
cl.option(“-r”, “–reverse”) {direction = :backward}
cl.parse(ARGV)
When will cl be collected, in the second piece of code? Never?
It’s just my opinion. Feel free to ignore me…
gegroet,
Erik V. - http://www.erikveen.dds.nl/