您可以编写一个迭代器,它“连接”其子级的迭代器,甚至可以懒惰地这样做。在
Song
的迭代器上调用
next()
将会遍历
Section
和
MusicComponent
迭代器,并最终提供下一个
MusicTime
。
Guava使这变得容易。将
MusicComponent
作为
Iterable<MusicTime>
,并实现
iterator()
:
@Override
public Iterator<MusicTime> iterator() {
return Iterables.concat(getChildren()).iterator();
}
由于所有的孩子都是 MusicComponent
,因此它们本身实现了 Iterable<MusicTime>
,Song
的迭代器将是 Section
迭代器的连接,这些迭代器本身是 MusicTime
迭代器的连接。
最后一个迭代器是一个特殊情况。一个 MusicTime
迭代器只应该返回自己一次:
@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);
}
现在,您可以在不重新实现递归的情况下执行任何类型的操作(例如播放、计算总时间等)。
不过,对于您的问题有替代解决方案,但这都取决于设计选择。例如,您可以在MusicComponent
上拥有一个play
方法,然后Song
和Section
通过调用其所有子级上的play
来实现它。这是一种简单的递归实现,但您必须为您打算添加到MusicComponent
上的所有操作(例如play
、getTotalDuration
等)重复递归。
如果您需要更多灵活性,则可以使用 Visitor设计模式并将播放操作制作成访问者(例如PlayVisitor
)。这样做的好处是您可以决定从访问者内部控制迭代顺序,但较难添加新的MusicComponent
实现。
java.lang.NoClassDefFoundError: com.google.common.collect.Iterables
。您有什么想法是什么原因引起的吗? - NiallMusicTime
作为第一个结果,但是否可能也获取包含它的Section
?这样当客户端遍历组件时,就可以决定如何处理它们。我能使用Iterables
来实现吗? - NiallSection
的accept(Visitor)
方法可以为其所有子元素调用visitSection
和visitMusicTime
。但是,您将失去迭代器所提供的“透明度”:访问者需要了解Section
和MusicTime
,而使用迭代器的类只需要了解MusicTime
。无论哪种方式,您都已经解决了问题,这很棒。 :-) - Mattias Buelens我可以向上遍历树以找到每个
MusicTime的
Section`。不确定这是否是最佳方法,但似乎运行良好。我将研究访问者模式,因为我认为我可能最终需要这种灵活性。 - Niall