views:

40

answers:

2

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 be 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.

+1  A: 

Hi,

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
Dan Kendall
Dan, thanks for your answer. Since I was kind of on the fence about using the visitor pattern already, Ian convinced me to leave it behind. Thanks anyway.
fireeyedboy
Hmm. Ok. Be aware though that Visitor is not specific to recursive structures.
Dan Kendall
A: 

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.

Ian
You convinced me to leave the visitor pattern behind. I wasn't aware that the visitor pattern is typically meant for recursive data. I decided to take the approach you suggest, although a bit more flexible, since I need to output partial fragments also. But you couldn't have known that. Thanks.
fireeyedboy