문제

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.

도움이 되었습니까?

해결책

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.

다른 팁

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
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top