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.
精彩评论