This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
I have a class that manages a list and users need to iterate over that list.
The way I see it, I have to basic alternatives:
# Give user access to the array and let them iterate over Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
or
# Provide a custom iterator
class Pea
def each_pod @pods.each { |pod| yield pod }
end
end
Pea.new.each_pod { |pod| ..do stuff.. }
end
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
This may be a silly design question, but I always balk at the right
answer when I am confronted with it.
I have a class that manages a list and users need to iterate over
that list. The way I see it, I have to basic alternatives:
# Give user access to the array and let them iterate over Array class
Pea attr_reader :pods end Pea.new.pods.each { |pod| ..do stuff.. }
or
# Provide a custom iterator class Pea def each_pod @pods.each { |pod|
yield pod } end end Pea.new.each_pod { |pod| ..do stuff.. } end
In other words, for classes that manage a list of items, do people
prefer to see a custom iterator, such as #each_<item>, or do they
prefer getting back an array and iterating over it themselves, such
as #<items>.each?
This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
I have a class that manages a list and users need to iterate over that list.
The way I see it, I have to basic alternatives:
# Give user access to the array and let them iterate over Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
Which exposes implementation somewhat, but....
or
# Provide a custom iterator
class Pea
def each_pod @pods.each { |pod| yield pod }
end
# Maybe:-
alias :each :each_pod
end
Pea.new.each_pod { |pod| ..do stuff.. }
end
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
because you might change your list to a Set or tree or soemthing
later. Unless you normally iterate over something else... (But having said that, I'd expect a Pod to contain Peas :-)!)
Don't you want peas in a pod and not the other way around
I think either is fine with a couple changes:
alternative #1: in the docs say that #pods returns an
Enumerable not an Array. This gives you the freedom to change
the implementation to use a Set or some other Enumerable to
hold the pods.
alternative #2: as long as you don't have multiple each
methods, call the method #each instead of #each_pod and include
Enumerable.
···
--- Jim Freeze <jim@freeze.org> wrote:
This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
I have a class that manages a list and users need to iterate
over that list.
The way I see it, I have to basic alternatives:
# Give user access to the array and let them iterate over
Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
or
# Provide a custom iterator
class Pea
def each_pod @pods.each { |pod| yield pod }
end
end
Pea.new.each_pod { |pod| ..do stuff.. }
end
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as
#each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
I don't think this is a silly question at all. I'm having to deal with it myself right now. I never seem to come up with the same answer twice in a row.
[snip]
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
There is another possibility, have the pods accessor return a (shallow) copy of the original.
I've done all three.
These days I just return the array and let the user do what they want. This saves me a lot of work at the risk of potentially ruining the integrity of the object structure. Seemed like a good idea at the time, but putting it that way... Actually I don't think it is that bad. If I was worried, I'd return a copy. I don't think I'd go the each_pod route. Who knows what I'm going to think tomorrow.
Hi, Jim. Don't worry. All design questions are silly, but I don't mind pontificating.
I think I'd return an Array or Set or something.
1. It's just easier to write the Pod class that way (as you showed).
2. It's just easier to use the Pod class that way. Array & Set map more closely to what you're exposing (a finite list with random access) and provide methods that might be helpful to the user, such as #+ and #empty?, that Enumerable doesn't provide.
However, as a rule of thumb, I'm lazy, so I may be biased.
If you don't like the idea of exposing your internal Array or Set, you're welcome to wrap it in some sort of Decorator class (say, to make it immutable) before returning it -- that's a common Java idiom (*ahem* I mean "pattern"; I must not have been served the right Kool-Aid). The awfully cool APIs will actually return a "live" Array, where modifying the array or its contents through typical-looking Array methods actually triggers the backend logic to do its thang.
Devin
Jim Freeze wrote:
···
This may be a silly design question, but I always balk at
the right answer when I am confronted with it.
I have a class that manages a list and users need to iterate over that list.
The way I see it, I have to basic alternatives:
# Give user access to the array and let them iterate over Array
class Pea
attr_reader :pods
end
Pea.new.pods.each { |pod| ..do stuff.. }
or
# Provide a custom iterator
class Pea
def each_pod @pods.each { |pod| yield pod }
end
end
Pea.new.each_pod { |pod| ..do stuff.. }
end
In other words, for classes that manage a list of items,
do people prefer to see a custom iterator, such as #each_<item>,
or do they prefer getting back an array and iterating
over it themselves, such as #<items>.each?
#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.
1) Pod.each { |pea| ... }. There is no indication what each will yield.
2) In some cases, there are multiple things that can be iterated through.
Maybe a better example:
#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.
Any comments on this?
I think if you're using pod/peas as a container/elements example, then
you should accept that the container contains the elements
pod.each {|pea| ... }
has enough pod/pea semantics to make it clear. In fact, this:
pod.each_pea {|pea| ... }
seems a bit redundant to me. (Unless there's something else that a
pod might iterate over, similar to String#each vs. String#each_byte.
Of course, lots of people wish that String#each did what
String#each_byte does
I still think the second way is just fine if you just say in
the docs that #wheels and #headlights returns an Enumerable.
Or if you really wanted to make sure there is no abuse, just
make #wheels and #headlights return an Enumerator (naming @wheels.each and @headlights.each as the each methods). Or if
you wanted to be v1.9-like make #each_wheel and #each_headlight
return an Enumerator when no block is supplied.
···
--- Jim Freeze <jim@freeze.org> wrote:
On 9/27/05, David A. Black <dblack@wobblini.net> wrote:
> Why not just call that #each? (And, as others have said,
possibly
> include Enumerable, though only if you need the other
Enumerable
> stuff).
Good question.
1) Pod.each { |pea| ... }. There is no indication what each
will yield.
2) In some cases, there are multiple things that can be
iterated through.
Maybe a better example:
#2 compromises the OO integrity. I understand that, but
I guess I am bugged by #1 where Pod.new.each yields a 'pea'.
If I see a Pod.each, I kind of expect to get a 'pod' back, not
a pea. That is the reason I did #each_pod.
Any comments on this?
what if youcalled the iterator function each_pea?
Pod.new.each_pea {|pea| ... }
Of course then certain Enumerable magic won't work...
But if you consider that a Pod is a container for Peas then even the 'each'
iterator makes sense, I think.
Ok, so you convinced me. Use #each. But, there still does not seem
to be a clear answer for an object having multiple containers.
Granted, there is precedence with things like #each_byte. But,
I wonder if it is better to use an argument to each.
Here are four methods that I can see as potential solutions:
No. (4) simply sets an internal state variable to indicate which container #each uses. Not pretty, and not thread safe, but permits Enumerable to
be used.
Do you have a preference for 1-4 or another solution?
···
On 9/27/05, David A. Black <dblack@wobblini.net> wrote:
I think if you're using pod/peas as a container/elements example, then
you should accept that the container contains the elements
pod.each {|pea| ... }
has enough pod/pea semantics to make it clear. In fact, this:
pod.each_pea {|pea| ... }
seems a bit redundant to me. (Unless there's something else that a
pod might iterate over, similar to String#each vs. String#each_byte.
Of course, lots of people wish that String#each did what
String#each_byte does
No. (4) simply sets an internal state variable to indicate which container #each uses. Not pretty, and not thread safe, but permits Enumerable to
be used.
I am curious what people think about these methods.
To all you pattern experts, is there a pattern for this
situation?
Ok, so you convinced me. Use #each. But, there still does not seem
to be a clear answer for an object having multiple containers.
Granted, there is precedence with things like #each_byte. But,
I wonder if it is better to use an argument to each.
I think people should require 'enumerator' for this, it creates Kernel#enum_for, so you can create a Enumerable::Enumerator instance
for every iterator:
somethings = obj.enum_for(:each_something)
Then you can iterate
somethings.each do |something|
# ...
end
and because Enumerator includes Enumerable, you can get an
array as well by calling
somethings_array = somethings.to_a
without breaking encapsulation.
This solves all your problems, and you only have to create an arbitrary named iterator method. I think enumerator should become part of Standard Ruby, because it's so damn useful.
No. (4) simply sets an internal state variable to indicate which container #each uses. Not pretty, and not thread safe, but permits Enumerable to
be used.
I am curious what people think about these methods.
To all you pattern experts, is there a pattern for this
situation?
No. (4) simply sets an internal state variable to indicate which
container #each uses. Not pretty, and not thread safe, but permits
Enumerable to
be used.
4. is definitely a non option. That's in the same league as containers glued together with an iterator (not #each). That's simply a don't do. It's thread unsafe and error prone. I strongly favor 3 with an optional variant of a read only proxy returned (using Enumerator for example). 2 is ok also and maybe 1, too. Normally OO suggests to have separate methods but since you can easily say this in Ruby 1 seems ok, too:
def each(cont, &b)
instance_variable_get("@#{cont}").each(&b)
self
end
IOW you don't need to touch this method when you add more containers.
I am curious what people think about these methods.
To all you pattern experts, is there a pattern for this
situation?
Not that I'm a pattern expert... I'll throw in my 0.02EUR anyway:
- My rule of thumb, if the object's main task is to be a container, give it an each method - this makes usage for clients convenient (example TreeNode, each will iterate child nodes).
- If there are several containers contained then make them accessible and let clients work with them. You save yourself a lot of hassle and name collisions (KISS).
- Remember: there's no way to protect the internals of an instance: if someone wants to screw up your instance he can always use send, instance_eval, instance_variable_get and *_set to access your innards.
On 9/27/05, Robert Klemme <bob.news@gmx.net> wrote:
- My rule of thumb, if the object's main task is to be a container, give it
an each method - this makes usage for clients convenient (example TreeNode,
each will iterate child nodes).
- If there are several containers contained then make them accessible
and let clients work with them. You save yourself a lot of hassle and name
collisions (KISS).