我正在尝试为类的层次结构编写迭代器,这些类共同构成歌曲的组成部分。所有类都是抽象MusicComponent
基类的实现,并继承了getChildren()
函数。抽象MusicTime
子类知道要播放的实际音符/和弦,其所有实现(例如quaver,crotchet)都会为null
返回getChildren()
。
其他组件为MusicComponent
,其中包含MusicTimes
的集合,例如一次一个栏,Section
,其中包含MusicComponents
。 Song
保留构成歌曲的Sections
,例如诗句,合唱,具有不同节奏/时间签名的部分。
我需要的是一个迭代器,它将遍历Sections
中的所有Song
,然后遍历MusicComponents
中的所有Section
,并且仅在找到{{1}时} descendant,根据音符类型以及包含MusicTime
的时间签名和速度播放时间长度的音符。
对不起,如果信息太多,但这是我能解释我正在尝试做什么的唯一方法。那么我是否需要使用堆栈来处理这个问题,记录Section
我已经访问过哪一个,或者只是使用递归来实现这一点?
答案 0 :(得分:3)
你可以编写一个迭代器来“连接”子代的迭代器,甚至是懒惰的。然后在next()
的迭代器上调用Song
将深入查看Section
和MusicComponent
迭代器,最后传递下一个MusicTime
。
Guava让这很容易。将MusicComponent
设为Iterable<MusicTime>
并将iterator()
实施为:
@Override
public Iterator<MusicTime> iterator() {
return Iterables.concat(getChildren()).iterator();
}
由于所有子节点都是MusicComponent
并因此自己实现Iterable<MusicTime>
,Song
的迭代器将是Section
迭代器的串联,它们本身是{MusicTime
的连接。 1}}迭代器。
这最后一个迭代器是一个特例。 MusicTime
迭代器应该只返回一次:</ p>
@Override
public Iterator<MusicTime> iterator() {
return Iterators.singletonIterator(this);
}
或者,Section
的迭代器可以替换为:
@Override
public Iterator<MusicTime> iterator() {
return getChildren().iterator();
}
有了这个,迭代就变得如此简单:
for (MusicTime time : song) {
player.play(time);
}
您现在可以执行任何类型的操作(播放,计算总持续时间......),而无需重新实现递归。
虽然有针对您的问题的替代解决方案,但这一切都归结为设计选择。例如,您可以在play
上使用MusicComponent
方法Song
和Section
通过在所有子项上调用play
来实现。这是一个简单的递归实现,但您必须为要在MusicComponent
上添加的所有操作重复递归(例如play
,getTotalDuration
,...)。
如果您需要更多灵活性,可以使用Visitor design pattern并将播放操作设为访问者(例如PlayVisitor
)。这样做的好处是,您可以决定从访问者中控制迭代顺序,但会增加新的MusicComponent
实现更加困难。
答案 1 :(得分:0)
你需要保留自己的筹码。迭代器在找到它时必须返回下一个值。如果你可以递归地写它,比如说recursiveMethod
调用自己,然后再调用它自己,然后在第三次调用它,它找出要返回的值,调用栈看起来像
recursiveMethod
recursiveMethod
recursiveMethod
theIterator.next
the client that's using the iterator
当最里面的recursiveMethod
返回时,它必须将值返回给客户端,然后客户端必须继续运行。这意味着必须弹出调用堆栈上的前四个条目。然后,当客户端想要下一个值,并再次调用theIterator.next()
时,必须重建调用堆栈上面的三个 recursiveMethod
条目,以便进程可以继续。 Java没有办法做到这一点(至少通过Java 7;如果Java 8这样做,我不会知道它,因为我不熟悉所有新功能)。
我知道有多个调用堆栈的唯一方法是拥有多个线程。我不建议为此目的使用线程,但也许其他人已经成功使用它。
您可能需要阅读Wikipedia article on coroutines。
答案 2 :(得分:0)
由于所有组件都继承自同一个抽象类MusicComponent
,因此我将在该类中实现默认方法play()
。假设MusicComponent
类也具有属性protected List<MusicComponent> children
,它将如下所示:
public void play(){
for(MusicComponent component: children){
component.play();
}
}
在MusicTime
课程中,我会覆盖play()方法以实际播放如下音乐:
@Override
public void play(){
//music playing logic
}
没有必要在其他类中重写此方法,因为它们只是无法播放音乐的复合类。
创建完整的组件树后。要播放所有音乐,您需要在最顶层的组件play()
上运行Song
方法:
song.play()
它会遍历所有孩子,最终到达播放音乐的最底层MusicTime
个对象。这个答案是基于我对你的问题的解释以及我对Composite Design Pattern的理解,我认为这是解决问题的可行方法。