⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pat5k.htm

📁 Design Pattern 设计模式
💻 HTM
📖 第 1 页 / 共 3 页
字号:
<A NAME="auto1042"></A><LI><EM>Adding new ConcreteElement classes is hard.</EM>The Visitor pattern makes it hard to add new subclasses of Element. Eachnew ConcreteElement gives rise to a new abstract operation on Visitor anda corresponding implementation in every ConcreteVisitor class. Sometimes adefault implementation can be provided in Visitor that can be inheritedby most of the ConcreteVisitors, but this is the exception rather thanthe rule.<A NAME="auto1043"></A><P>So the key consideration in applying the Visitor pattern is whether youare mostly likely to change the algorithm applied over an objectstructure or the classes of objects that make up the structure. TheVisitor class hierarchy can be difficult to maintain when newConcreteElement classes are added frequently. In such cases, it'sprobably easier just to define operations on the classes that make upthe structure. If the Element class hierarchy is stable, but you arecontinually adding operations or changing algorithms, then the Visitorpattern will help you manage the changes.</P></LI><A NAME="trav-across-class"></A><LI><EM>Visiting across class hierarchies.</EM>An iterator (see <A HREF="pat5dfs.htm" TARGET="_mainDisplayFrame">Iterator (257)</A>) can visit the objects in astructure as it traverses them by calling their operations. But an iteratorcan't work across object structures with different types of elements. Forexample, the Iterator interface defined on<A HREF="pat5dfs.htm#class_Iterator_declaration" TARGET="_mainDisplayFrame">page 263</A> can access only objects of type <CODE>Item</CODE>:<A NAME="auto1044"></A><PRE>    template &lt;class Item>    class Iterator {        // ...        Item CurrentItem() const;    };</PRE><A NAME="auto1045"></A><P>This implies that all elements the iterator can visit have a common parentclass <CODE>Item</CODE>.</P><A NAME="auto1046"></A><P>Visitor does not have this restriction.  It can visit objects thatdon't have a common parent class. You can add any type of object to aVisitor interface.  For example, in</P><A NAME="auto1047"></A><PRE>    class Visitor {    public:        // ...        void VisitMyType(MyType*);        void VisitYourType(YourType*);    };</PRE><A NAME="auto1048"></A><P><CODE>MyType</CODE> and <CODE>YourType</CODE> do not have to be related throughinheritance at all.</P></LI><A NAME="auto1049"></A><P></P><A NAME="accumulatingstate"></A><LI><EM>Accumulating state.</EM>Visitors can accumulate state as they visit each element in the objectstructure.  Without a visitor, this state would be passed as extraarguments to the operations that perform the traversal, or theymight appear as global variables.</LI><A NAME="auto1050"></A><P></P><A NAME="breakencap"></A><LI><EM>Breaking encapsulation.</EM>Visitor's approach assumes that the ConcreteElement interface is powerfulenough to let visitors do their job.  As a result, the pattern oftenforces you to provide public operations that access an element'sinternal state, which may compromise its encapsulation.</LI></OL><A NAME="implementation"></A><H2><A HREF="#samplecode"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Sample Code"></A> Implementation</H2> <A NAME="auto1051"></A><P>Each object structure will have an associated Visitor class.  Thisabstract visitor class declares a VisitConcreteElement operation foreach class of ConcreteElement defining the object structure.  EachVisit operation on the Visitor declares its argument to be aparticular ConcreteElement, allowing the Visitor to access theinterface of the ConcreteElement directly.  ConcreteVisitor classesoverride each Visit operation to implement visitor-specific behaviorfor the corresponding ConcreteElement class.</P><A NAME="auto1052"></A><P>The Visitor class would be declared like this in C++:</P><A NAME="auto1053"></A><PRE>    class Visitor {    public:        virtual void VisitElementA(ElementA*);        virtual void VisitElementB(ElementB*);            // and so on for other concrete elements    protected:        Visitor();    };</PRE><A NAME="auto1054"></A><P>Each class of ConcreteElement implements an <CODE>Accept</CODE> operationthat calls the matching <CODE>Visit...</CODE> operation on the visitorfor that ConcreteElement.  Thus the operation that ends up gettingcalled depends on both the class of the element and the class of thevisitor.<A NAME="fn10"></A><SUP><A HREF="#footnote10">10</A></SUP></P><A NAME="auto1055"></A><P>The concrete elements are declared as</P><A NAME="auto1056"></A><PRE>    class Element {    public:        virtual ~Element();        virtual void Accept(Visitor&amp;) = 0;    protected:        Element();    };        class ElementA : public Element {    public:        ElementA();        virtual void Accept(Visitor&amp; v) { v.VisitElementA(this); }    };        class ElementB : public Element {    public:        ElementB();        virtual void Accept(Visitor&amp; v) { v.VisitElementB(this); }    };</PRE><A NAME="auto1057"></A><P>A <CODE>CompositeElement</CODE> class might implement <CODE>Accept</CODE>like this:</P><A NAME="auto1058"></A><PRE>    class CompositeElement : public Element {    public:        virtual void Accept(Visitor&amp;);    private:        List&lt;Element*>* _children;    };        void CompositeElement::Accept (Visitor&amp; v) {        ListIterator&lt;Element*> i(_children);            for (i.First(); !i.IsDone(); i.Next()) {            i.CurrentItem()->Accept(v);        }        v.VisitCompositeElement(this);    }</PRE><A NAME="auto1059"></A><P>Here are two other implementation issues that arise when you apply theVisitor pattern:</P><OL><A NAME="doubledispatch"></A><A NAME="singledispatch"></A><LI><EM>Double dispatch.</EM>Effectively, the Visitor pattern lets you add operations to classeswithout changing them.  Visitor achieves this by using a techniquecalled <STRONG>double-dispatch</STRONG>.  It's a well-known technique.  Infact, some programming languages support it directly (CLOS, forexample).  Languages like C++ and Smalltalk support<STRONG>single-dispatch</STRONG>.<A NAME="auto1060"></A><P>In single-dispatch languages, two criteria determine which operationwill fulfill a request: the name of the request and the type ofreceiver. For example, the operation that a GenerateCode request willcall depends on the type of node object you ask. In C++, calling<CODE>GenerateCode</CODE> on an instance of <CODE>VariableRefNode</CODE> willcall <CODE>VariableRefNode::GenerateCode</CODE> (which generates code for avariable reference). Calling <CODE>GenerateCode</CODE> on an<CODE>AssignmentNode</CODE> will call<CODE>AssignmentNode::GenerateCode</CODE> (which will generate code for anassignment). The operation that gets executed depends both on the kindof request and the type of the receiver.</P><A NAME="auto1061"></A><P>"Double-dispatch" simply means the operation that gets executeddepends on the kind of request and the types of <EM>two</EM> receivers.<CODE>Accept</CODE> is a double-dispatch operation.  Its meaning dependson two types: the Visitor's and the Element's.  Double-dispatchinglets visitors request different operations on each class ofelement.<A NAME="fn11"></A><SUP><A HREF="#footnote11">11</A></SUP></P><A NAME="auto1062"></A><P>This is the key to the Visitor pattern: The operation that getsexecuted depends on both the type of Visitor and the type of Elementit visits.  Instead of binding operations statically into the Elementinterface, you can consolidate the operations in a Visitor and use<CODE>Accept</CODE> to do the binding at run-time.  Extending the Elementinterface amounts to defining one new Visitor subclass rather than many newElement subclasses.</P></LI><A NAME="trav-responsibility"></A><LI><EM>Who is responsible for traversing the object structure?</EM>A visitor must visit each element of the object structure. The questionis, how does it get there? We can put responsibility for traversal inany of three places: in the object structure, in the visitor, or in aseparate iterator object (see <A HREF="pat5dfs.htm" TARGET="_mainDisplayFrame">Iterator (257)</A>).<A NAME="auto1063"></A><P>Often the object structure is responsible for iteration. A collectionwill simply iterate over its elements, calling the Accept operation oneach. A composite will commonly traverse itself by having each Acceptoperation traverse the element's children and call Accept on each ofthem recursively.</P><A NAME="iter-ext-int"></A><P>Another solution is to use an iterator to visit the elements. In C++,you could use either an internal or external iterator, depending on whatis available and what is most efficient. In Smalltalk, you usually usean internal iterator using <CODE>do:</CODE> and a block. Since internaliterators are implemented by the object structure, using an internaliterator is a lot like making the object structure responsible foriteration. The main difference is that an internal iterator will notcause double-dispatching&#151;it will call an operation on the <EM>visitor</EM> with an <EM>element</EM> as an argument as opposed to calling anoperation on the <EM>element</EM> with the <EM>visitor</EM> as an argument.But it's easy to use the Visitor pattern with an internal iterator ifthe operation on the visitor simply calls the operation on the elementwithout recursing.</P><A NAME="auto1064"></A><P>You could even put the traversal algorithm in the visitor, although you'llend up duplicating the traversal code in each ConcreteVisitor for eachaggregate ConcreteElement. The main reason to put the traversal strategyin the visitor is to implement a particularly complex traversal, onethat depends on the results of the operations on the object structure.We'll give an example of such a case in the Sample Code.</P></LI></OL><A NAME="samplecode"><A><H2><A HREF="#knownuses"><IMG SRC="gifsb/down3.gif" BORDER=0 ALT="next: Known Uses"></A> Sample Code</H2> <A NAME="auto1065"></A><P>Because visitors are usually associated with composites, we'll use the<CODE>Equipment</CODE> classes defined in the Sample Code of<A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A> to illustrate the Visitor pattern.  Wewill use Visitor to define operations for computing theinventory of materials and the total cost for a piece of equipment.The <CODE>Equipment</CODE> classes are so simple that using Visitorisn't really necessary, but they make it easy to see what'sinvolved in implementing the pattern.</P><A NAME="equipment"></A><P>Here again is the <CODE>Equipment</CODE> class from<A HREF="pat4cfs.htm" TARGET="_mainDisplayFrame">Composite (163)</A>.  We've augmented it with an<CODE>Accept</CODE> operation to let it work with a visitor.</P><A NAME="auto1066"></A><PRE>    class Equipment {    public:        virtual ~Equipment();            const char* Name() { return _name; }            virtual Watt Power();        virtual Currency NetPrice();        virtual Currency DiscountPrice();            virtual void Accept(EquipmentVisitor&amp;);    protected:        Equipment(const char*);    private:        const char* _name;    };</PRE><A NAME="auto1067"></A><P>The <CODE>Equipment</CODE> operations return the attributes of a piece ofequipment, such as its power consumption and cost.  Subclasses redefinethese operations appropriately for specific types of equipment (e.g.,a chassis, drives, and planar boards).</P><A NAME="auto1068"></A><P>The abstract class for all visitors of equipment has a virtualfunction for each subclass of equipment, as shown next.  All of thevirtual functions do nothing by default.</P><A NAME="equip-visit"></A><PRE>    class EquipmentVisitor {    public:        virtual ~EquipmentVisitor();            virtual void VisitFloppyDisk(FloppyDisk*);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -