Implementing visitor pattern on aggregate objects
I'm struggling with applying the visitor pattern on some objects that have scalar members and at the same time aggregate members (collections).
These are the objects that I have:
Artist
- id
- name
- .. more scalar values ..
- worksOfArt <-- this is a collection as WorkOfArt instances
WorkOfArt
- id
- name
- .. more scalar values ..
- artists <-- this is a collection of Artist instances
As you can see, the structure will also b开发者_运维问答e recursive, but that's of later concern to me. ;-)
My question is: what is the best way to implement the visitor pattern, that allows me visit the objects and also only their visitable children (the collections).
I thought creating an interface like this:
VisitableAggregateInterface
{
public function getVisitableChildren(); // this would return only visitable children
}
And then let both Artist and WorkOfArt extend an abstract class like this:
VisitableAggregateAbstract implements VisitableAggregateInterface
{
public function accept( Visitor $visitor )
{
$visitor->visit( $this );
foreach( $this->getVisitableChildren() as $visitableChild )
{
$visitableChild->accept( $visitor );
}
}
/*
VisitableAggregateInterface::getVisitableChildren()
will be implemented by Artist and WorkOfArt and will only
return visitable children (like collections), and not scalar values.
*/
}
The goal is ultimately to end up with a concrete Visitor that will write out an XML file similar to this:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<artgallery>
<artists>
<artist>
<id>1</id>
<name></name>
<worksOfArt>
<workOfArt refid="11"/>
<workOfArt refid="12"/>
</worksOfArt>
<artist>
<artists>
<worksOfArt>
<workOfArt>
<id>11</id>
<artists>
<artist refid="1"/>
</artists>
<name></name>
<info><![CDATA[some info]]></info>
</workOfArt>
<workOfArt>
<id>12</id>
<artists>
<artist refid="1"/>
</artists>
<name></name>
<info><![CDATA[some info]]></info>
</workOfArt>
</worksOfArt>
</artgallery>
Please advice: Am I going in the right direction here? Because the getVisitableChildren()
interface feels a bit quirky to me. Should I perhaps maybe even ditch the visitor pattern altogether and take a different approach?
Thanks.
I've seen Visitor
implemented using a 'traverser' class that has specific knowledge of the types to be visited. In this case it would 'know' to visit WorksOfArt
in Artists
but not Artists
in WorksOfArt
. You can define other traversers for other behaviour.
Dan
Here's some pseudo-code I've dug out... (vb actually, but don't tell anyone :)):
Visitable:
Public Interface IVisitable
Sub accept(ByVal visitor As IVisitor)
End Interface
Visitor:
Public Interface IVisitor
Sub visit(ByVal visitable As IVisitable)
End Interface
Traverser:
Public Class PaymentListExportTraverser
Private payments As PaymentList
Private visitor As IVisitor
Public Sub New(ByVal paymentList As PaymentList, ByVal exportVisitor As IVisitor)
payments = paymentList
visitor = exportVisitor
End Sub
Public Sub traverse()
For Each p As Payment In payments
p.accept(visitor)
Next
End Sub
End Class
You don't actually state what the relation between artist and artwork is. If, as I suspect, both mean the Artist made the WorkOfArt, then you don't have a recursive data structure, so you don't need a visitor pattern.
I would simply "brute force" it. Something like this (untested)
echo "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>
<artgallery>
<artists>\n";
Foreach (Artists as Artist) {
echo " <artist>
<id>1</id>
<name></name>
<worksOfArt>\n";
ForEach (Artist.worksOfArt as Work) {
$refid = ????;
echo "<workOfArt refid=\"$refid"/>\n";
}
echo " </worksOfArt>
</artist>\n";
}
echo " </artists>
<worksOfArt>";
Foreach (WorkOfArt as work) {
ForEach (work.Artists as Artist) {
}
}
Then just put the rest of echo statements into the above
BTW - you have a typo in the question. The close of and need /s added.
精彩评论