When I did my extended error reporting modification to LyX, I added 
Inset Visitors. I found them to be very useful...

For those unfamiliar with the Visitor pattern, I strongly suggest you 
grab a copy of "Design Patterns", by Gamma et al. See page 331. Well 
worth the read... but, I'll attempt to summarise the properties of the 
pattern here.

The Visitor pattern is useful for processing different elements in a 
polymorphic structure (eg the document tree). It allows you to define 
operations upon different classes (eg Inset types) differently depending 
upon the class type, without modifying the original class. Consider the 
following infrastructure:


class InsetVisitor;

class Inset {
// blah blah
    virtual void Accept(InsetVisitor&) = 0;
};

class InsetA : public Inset {
// blah blah
    virtual void Accept(InsetVisitor& iv ) { iv.VisitInsetA(*this); }
};

class InsetB : public Inset {
// blah blah
    virtual void Accept(InsetVisitor& iv ) { iv.VisitInsetB(*this); }
};


class InsetVisitor {
// blah blah
    virtual void VisitInsetA( InsetA& ) = 0;
    virtual void VisitInsetB( InsetB& ) = 0;
};


I may now extract the, for example, spell-checking methods from 
LyXParagraph, and define a single visitor which knows what to do to any 
particular kind of inset to perform spell-checking on it (that was a bad 
example to pick, maybe someone else can come up with a better one...):

class SpellCheckInsetVisitor : public InsetVisitor {
 // blah blah
    virtual void VisitInsetA( InsetA& i ) {
        // Carry out spellcheck on contents of InsetA
     }
    virtual void VisitInsetB( InsetB& i ) {
        // InsetB doesn't have any text that you can spellcheck, so do 
nothing.
    }
};

This means that all the 'spellchecking' (or whatever) code related to a 
single kind of operation is contained within a single visitor class 
instead of being strewn all over the code in methods of individual insets.

To use it, the spell checker driver does this:
SpellCheckerVisitor spell;
for( InsetIterator i = insets.begin(); i != insets.end(); ++i ) {
    i->Accept(spell);
}

(I usually define some fancier ways of doing this so that information 
can be passed to the spell checker object, but this is simplest for 
illustration.)

The call to Inset::Accept bounces to the visitor's VisitInsetA or 
VisitInsetB method depending on what type of inset it is, according to 
the vtable ie it's efficient.

I actually used these for traversing the document and collecting labels, 
citations, bibliography boxes, and ERTs for use in error reporting, but 
they have so many other uses as well. The wonderful thing about it is 
that you only need to add one method to every inset - the Accept method. 
 From then on, you are free to define new operations upon insets without 
touching the insets themselves...

Note that the operations contained within the visitor classes can only 
utilise public interfaces on the insets they manipulate. This means that 
the Inset interface must be complete. - no fiddling with private innards 
may be done this way.

This pattern does have a disadvantage: If you keep adding new insets 
types, you have to go and add methods for them all to all the different 
kinds of inset visitors. This may be overcome by creating an inset 
visitor class which does nothing - ie all abstract methods from the base 
class are implemented as no-ops. Actual operation visitors would then 
inherit from this and only override the methods that they need. However, 
this leads to 'forgetting' to add the required methods to the visitors 
in the few cases where they are really necessary, as the compiler will 
no longer complain... you will just get a silent nop where perhaps you 
expected to spellcheck the contents of a caption. These kinds of 
problems are easy to fix, just not so easy to detect.

Anyway, I found that using this pattern gave me incredible flexibility 
in implementing my extended error handling code, and I wondered if 
anyone else thought there might be applications for it in the current 
LyX code base.

Ben.


Reply via email to