问题
I am trying to write an iterator for a hierarchy of classes that collectively make up the components of a song. All classes are implementations of the abstract MusicComponent base class and inherit a getChildren() function. The abstract MusicTime subclass knows the actual note/chord to play, and all its implementations (e.g. quaver, crotchet) return null for getChildren().
The other components are MusicComponent which holds a collection of MusicTimes e.g. a bar at a time, and Section which holds the MusicComponents. Song holds the Sections that make up the song e.g. verses, choruses, sections with different tempos/time signatures.
What I need is an iterator that will iterate through all Sections in a Song, then all MusicComponents in the Section and only when it finds a MusicTime descendant, play the note for the length of time based on its note type, and the time signature and tempo of its containing Section.
Sorry if too much info, but was the only way I could explain what I'm trying to do. So do I need to handle this with a stack, recording which MusicComponents I've visted or is there a way to do this just using recursion?
回答1:
You can write an iterator which "concatenates" the iterators of its children, even lazily. Calling next() on a Song's iterator would then drill down through the Section and MusicComponent iterators and finally deliver the next MusicTime.
Guava makes this easy. Make MusicComponent an Iterable<MusicTime> and implement iterator() as:
@Override
public Iterator<MusicTime> iterator() {
return Iterables.concat(getChildren()).iterator();
}
Since all children are MusicComponents and thus implement Iterable<MusicTime> themselves, Song's iterator will be a concatenation of Section iterators, which are themselves concatenations of MusicTime iterators.
This last iterator is a special case. A MusicTime iterator should only return itself once:
@Override
public Iterator<MusicTime> iterator() {
return Iterators.singletonIterator(this);
}
Alternatively, Section's iterator could be replaced with:
@Override
public Iterator<MusicTime> iterator() {
return getChildren().iterator();
}
With this, iterating becomes as easy as:
for (MusicTime time : song) {
player.play(time);
}
You can now do any kind of operation (playing, counting the total duration,...) without re-implementing the recursion.
There are alternative solutions for your problem though, but it all comes down to design choices. For example, you could have a play method on MusicComponent which Song and Section would implement by calling play on all of their children. This is a straightforward recursive implementation, but you must repeat the recursion for all operations you intend to add on MusicComponent (such as play, getTotalDuration, ...).
If you need more flexibility, you could use the Visitor design pattern and make your play operation a visitor (e.g. PlayVisitor). This has the advantage that you can decide to control the iteration order from within the visitor, but makes it harder to add new MusicComponent implementations.
回答2:
You'll need to keep your own stack. An iterator has to return the next value when it finds it. If you could write it recursively, say with recursiveMethod calling itself, and then calling itself again, and then inside the third time it's called, it finds out what value to return, the call stack would look something like
recursiveMethod
recursiveMethod
recursiveMethod
theIterator.next
the client that's using the iterator
When the innermost recursiveMethod returns, it has to return the value to the client, and then the client has to keep running. That means that the top four entries on the call stack have to be popped. Then, when the client wants the next value, and it calls theIterator.next() again, the above three recursiveMethod entries on the call stack would have to be reconstructed so that the process could continue. Java doesn't have a way to do that (at least through Java 7; if Java 8 does this, I wouldn't know about it since I'm not familiar with all the new features).
The only way that I know of to have multiple call stacks is to have multiple threads. I wouldn't recommend using threads for this purpose, but perhaps others have had successes with it.
You might want to read the Wikipedia article on coroutines.
回答3:
Since all of the components inherit from the same abstract class MusicComponent, I would implement the default method play() in that class. Assuming that MusicComponent class also has the property protected List<MusicComponent> children it would look like this:
public void play(){
for(MusicComponent component: children){
component.play();
}
}
In the MusicTime class, I would override the play() method to actually play the music like below:
@Override
public void play(){
//music playing logic
}
It should not be necessary to override this method in the other classes since they are just composite classes that can't play music.
After you've created your full component tree. To play all the music, you would need to run the play() method on the topmost component which is Song:
song.play()
It will iterate through all the children eventually reaching the bottommost MusicTime objects where the music will be played. This answer is based on my interpretation of your problem and my understanding of the Composite Design Pattern which I think is a viable way to solve your problem.
来源:https://stackoverflow.com/questions/21708867/creating-a-recursive-iterator