Given that you can't (or, rather, won't) use any existing collection-type classes (Array
, Hash
), which would be the simplest approach by far, I'd say your solution is ok. You could do a linked list implementation or something (a common way to implement a collection structure), but I'll leave such experiments to you.
So sure, it's kinda hacky, but it works.
It can be cleaned up a little, though.
Your OutOFBoundsException
has a typo (OF
rather than Of
), and I'd prefer the suffix Error
, as it inherits from an *Error
class.
However, the more appropriate class to inherit from is probably IndexError
. Which you could actually just use directly; no need to subclass. Slightly more generic than saying out-of-bounds, but still a good fit.
If you do want to subclass it anyway, I'd suggest nesting it under FixedArray
rather than in the global scope, just to minimize footprint.
As for the FixedArray
class itself, if we're keeping the array-elements-as-instance-variables approach, I'd suggest the following:
- Implement the customary
[]
and []=
element accessor methods (and use the latter in the initializer to set the nil
values rather than repeat the instance_variable_set
logic in two places)
- Extract the numeric-index-to-variable-name logic into a private method to avoid duplicating how that string is built, and to have a single place to check for out-of-bounds
- Define an
each
method and include Enumerable
to get a bunch of stuff for free (like to_a
, map
, and so forth).
I'd also include some sanity-checking of the size
argument given to the initializer; it has to be an integer, and it must be >= 0. Checking for out-of-bounds is reliant on the bounds being sensible, after all.
Another small thing: instance_variable_set
already returns the value that's being set, so there's no need for the extra line to return value
.
And you might want to add a to_s
and inspect
methods for good measure, but I'll leave that to you (you can just delegate them to to_a
and let Array
handle it, if you want).
I end up with something like this:
class FixedArray
# This can be skipped in favor of using IndexError directly
class OutOfBoundsError < IndexError; end
include Enumerable
attr_reader :size
def initialize(size)
raise ArgumentError if !size.is_a?(Integer) || size < 0
@size = size
size.times { |i| self[i] = nil }
end
def [](i)
instance_variable_get(name_for_index(i))
end
def []=(i, value)
instance_variable_set(name_for_index(i), value)
end
def each(&block)
size.times { |i| yield self[i] }
end
private
def name_for_index(index)
raise OutOfBoundsError unless (0...size).include?(index)
"@index#{index}"
end
end
Semantically, you might say that name_for_index
should raise an ArgumentError
if the index
it's given isn't an integer at all. Saying out-of-bounds is a little misleading. However, saying IndexError
would make sense whether the index is out-of-bounds or not a number at all, so that might be a reason to use IndexError
directly instead of adding a custom out-of-bounds error class.
Last thing about errors/exceptions: Add a useful description. I haven't done so in the code above, but it's definitely worth saying why, say, a ArgumentError
occurred in the initializer rather than just raising it with no explanation.