proof-of-concept visitor implementation now working

This commit is contained in:
Fischlurch 2008-01-04 01:44:40 +01:00
parent 527ce96f83
commit 809301a60e
3 changed files with 276 additions and 100 deletions

View file

@ -5,28 +5,88 @@
// 8/07 - how to control NOBUG??
// execute with NOBUG_LOG='ttt:TRACE' bin/try
#include <syslog.h>
// 1/08 - working out a static initialisation problem for Visitor (Tag creation)
#include <nobug.h>
#include <iostream>
//NOBUG_CPP_DEFINE_FLAG(ttt);
NOBUG_CPP_DEFINE_FLAG_LIMIT(ttt, LOG_WARNING);
using std::string;
using std::cout;
NOBUG_CPP_DEFINE_FLAG(test);
template<class TOOL> class Tag;
template<class TOOL, class TOOLImpl>
class TagTypeRef
{
public:
static Tag<TOOL> tag;
};
template<class TOOL>
class Tag
{
size_t tagID;
static size_t lastRegisteredID;
public:
Tag(size_t tt=0) : tagID(tt) {}
operator size_t() const { return tagID; }
template<class TOOLImpl>
static Tag<TOOL>&
get (TOOLImpl* const concreteTool=0)
{
INFO (test,"getTag");
Tag<TOOL>& t = TagTypeRef<TOOL,TOOLImpl>::tag;
if (!t)
t.tagID = ++Tag<TOOL>::lastRegisteredID;
return t;
}
};
/** storage for the Tag registry for each concrete tool */
template<class TOOL, class TOOLImpl>
Tag<TOOL> TagTypeRef<TOOL,TOOLImpl> ::tag (0);
template<class TOOL>
size_t Tag<TOOL>::lastRegisteredID (0);
class TT
{
};
class TI : public TT
{
};
class TII : public TT
{
};
int main (int argc, char* argv[])
{
NOBUG_INIT;
TRACE(ttt,"trace");
INFO(ttt,"info");
NOTICE(ttt,"notice");
WARN(ttt,"warning");
ERROR(ttt,"error");
TRACE(NOBUG_ON,"allways on?");
size_t xxx = Tag<TT>::get<TII>();
cout << "created Tag=" << xxx <<"\n";
cout << "created Tag=" << Tag<TT>::get<TI> () <<"\n";
return 0;
}

View file

@ -30,11 +30,12 @@
**
** Basic considerations
** <ul><li>cyclic dependencies should be avoided or at least restricted
** to some library related place</li>
** to some library related place. The responsibilities for
** user code should be as small as possible.</li>
** <li>Visitor is about <i>double dispatch</i>, thus we can't avoid
** using some table lookup implementation, and we can't avoid using
** some of the cooperating classe's vtables. Besides that, the
** implementation should not be too wastefull</li>
** some of the cooperating classes vtables. Besides that, the
** implementation should not be too wastefull...</li>
** <li>individual Visiting Tool implementation classes should be able
** to opt in or opt out on implementing functions treating some of
** the visitable subclasses.</li>
@ -53,13 +54,13 @@
#include "common/test/run.hpp"
//#include "common/factory.hpp"
//#include "common/util.hpp"
#include "common/singleton.hpp"
#include <boost/format.hpp>
#include <iostream>
#include <vector>
using cinelerra::Singleton;
using boost::format;
using std::string;
using std::cout;
@ -70,60 +71,84 @@ namespace cinelerra
namespace visitor
{
// ================================================================== Library ====
template<class TOOL> class Tag;
template<class TOOL, class TOOLImpl>
struct TagTypeRegistry
{
static Tag<TOOL> tag;
};
template<class TOOL>
class Tag
{
size_t tagID;
template<class TOOLImpl>
struct TagTypeRef { static Tag tag; };
static size_t lastRegisteredID;
public:
Tag() : tagID(0) { }
operator size_t() const { return tagID; }
template<class TOOLImpl>
static Tag
get(TOOLImpl* concreteTool=0)
static Tag&
get (TOOLImpl* const concreteTool=0)
{
Tag& t = TagTypeRef<const TOOLImpl>::tag;
TODO ("concurrent access");
TODO ("race condition");
Tag& t = TagTypeRegistry<TOOL,TOOLImpl>::tag;
if (!t)
t = ++lastRegisteredID;
t.tagID = ++lastRegisteredID;
return t;
}
};
/** storage for the Tag registry for each concrete tool */
template<class TOOL, class TOOLImpl>
Tag<TOOL> Tag<TOOL>::TagTypeRef<TOOLImpl>::tag;
Tag<TOOL> TagTypeRegistry<TOOL,TOOLImpl>::tag;
template<class TOOL>
size_t Tag<TOOL>::lastRegisteredID (0);
/** Marker interface "visiting tool".
*/
/** Marker interface "visiting tool" */
template<typename RET>
class Tool
{
public:
typedef RET ReturnType;
typedef Tool<RET> ToolBase;
typedef Tool<RET> ToolBase; ///< for templating the Tag and Dispatcher
virtual ~Tool () { }; ///< use RTTI for all visiting tools
/** allows discovery of the concrete Tool type
* when dispatching a visitor call */
virtual Tag<ToolBase>
getTag()
{
return Tag<ToolBase>::get(this);
/** allows discovery of the concrete Tool type when dispatching a
* visitor call. Can be implemented by inheriting from ToolType */
virtual Tag<ToolBase> getTag() = 0;
};
/** Mixin for attaching a type tag to the concrete tool implementation */
template<class TOOLImpl, class BASE=Tool<void> >
class ToolType : public BASE
{
typedef typename BASE::ToolBase ToolBase;
public:
virtual Tag<ToolBase> getTag()
{
TOOLImpl* typeref = 0;
return Tag<ToolBase>::get (typeref);
}
};
@ -138,7 +163,7 @@ namespace cinelerra
template<class TAR, class TOOL>
class Dispatcher
{
typedef TOOL::ReturnType ReturnType;
typedef typename TOOL::ReturnType ReturnType;
/** generator for Trampoline functions,
* used to dispatch calls down to the
@ -146,15 +171,15 @@ namespace cinelerra
* concrete tool implementation class
*/
template<class TOOLImpl>
ReturnType
static callTrampoline (TAR& obj, TOOL tool)
static ReturnType
callTrampoline (TAR& obj, TOOL& tool)
{
// cast down to real implementation type
REQUIRE (INSTANCEOF (TOOLImpl, &tool));
TOOLImpl& toolObj = static_cast<TOOLImpl&> (tool);
// trigger (compile time) overload resolution
// based on concrete type, and dispatch call.
// based on concrete type, then dispatch the call.
// Note this may cause obj to be upcasted.
return toolObj.treat (obj);
}
@ -164,50 +189,99 @@ namespace cinelerra
/** VTable for storing the Trampoline pointers */
std::vector<Trampoline> table_;
inline bool
is_known (size_t id)
{
return id<=table_.size() && table_[id-1];
}
inline void
storePtr (size_t id, Trampoline func)
{
TODO ("error- and concurrency handling");
if (id>table_.size())
table_.resize (id); // performance bottleneck?? TODO: measure the impact!
table_[id-1] = func;
}
inline Trampoline
storedTrampoline (size_t id)
{
if (id<=table_.size() && table_[id-1])
return table_[id-1];
else
return &errorHandler;
}
static ReturnType
errorHandler (TAR&, TOOL&)
{
cout << "unregistered combination of (Tool, Targetobject) invoked!\n";
}
public:
ReturnType
static Singleton<Dispatcher<TAR,TOOL> > instance;
inline ReturnType
forwardCall (TAR& target, TOOL& tool)
{
// get concrete type via tool's VTable
Tag<TOOL> index = tool.getTag();
Trampoline func = this->table_[index];
TODO ("Errorhandling");
return (*func) (target, tool);
return (*storedTrampoline(index)) (target, tool);
}
template<class TOOLImpl>
static inline void
enroll()
inline void
enroll(TOOLImpl* typeref)
{
TODO ("skip if already registered");
TODO ("concurrency handling");
Tag<TOOL> index = Tag<TOOL>::get<TOOLImpl>();
Trampoline func = callTrampoline<TOOLImpl>;
this->table_[index] = func;
Tag<TOOL>& index = Tag<TOOL>::get (typeref);
if (is_known (index))
return;
else
{
Trampoline func = &callTrampoline<TOOLImpl>;
storePtr (index, func);
}
}
};
/** storage for the dispatcher table(s) */
template<class TAR, class TOOL>
Singleton<Dispatcher<TAR,TOOL> > Dispatcher<TAR,TOOL>::instance;
/**
* concrete visiting tool implementation has to inherit from this
* class for each kind of calls it wants to get dispatched,
* Allowing us to record the type information.
*/
template<class TAR, class TOOLImpl>
template<class TAR, class TOOLImpl, class BASE=Tool<void> >
class Applicable
{
typedef TOOLImpl::ToolBase Base;
typedef TOOLImpl::ToolBase::ReturnType Ret;
typedef typename BASE::ReturnType Ret;
typedef typename BASE::ToolBase ToolBase;
protected:
Applicable ()
{
Dispatcher<TAR,Base>::enroll<TOOLImpl>();
TOOLImpl* typeref = 0;
Dispatcher<TAR,ToolBase>::instance().enroll (typeref);
}
virtual ~Applicable () {}
public:
virtual Ret treat (TAR& target) = 0;
// we could enforce the definition of treat()-functions by:
//
// virtual Ret treat (TAR& target) = 0;
};
@ -215,8 +289,8 @@ namespace cinelerra
// entweder die "halb-zyklische" Lösung, wo sich das einzelne
// Tool noch über den Dispatcher (als Vaterklasse) einklinken muß
// - vorteil: kann volle Auflösung (Konstruktoren-Trick mit rückgeführtem Typparameter
// - Nachteil: einzelnes visitable hängt von allen tools ab
// - Vorteil: kann volle Auflösung (Konstruktoren-Trick mit rückgeführtem Typparameter
// - Nachteil: einzelnes Visitable hängt von allen tools ab
//
// oder die TMP-Lösung, die auf der Applicable<TOOLTYPE, TAR> beruht und via Tabelle arbeitet.
// - Vorteil: der notwendige Kontext ist auf natürliche Weise da
@ -230,6 +304,8 @@ namespace cinelerra
// Frage ist also: wie könnte ich die Generierung des fehlenden Kontextes für die fehlenden Kombinationen
// antreiben? Einsprung natürlich über Interface/virt.Funktionen.
// Idee: der Benutzer deklariert Fallback<Types<A,B,C>, ToolType >
// bessere Idee: beides zusammen über die geniale "Applicable"-Basisklasse (wie in Loki)
// vom User-Code definieren lassen!
/** Marker interface "visitable object".
@ -239,27 +315,32 @@ namespace cinelerra
>
class Visitable
{
public:
/** to be defined by the DEFINE_PROCESSABLE_BY macro
* in all classes wanting to be treated by some tool */
typedef TOOL::ReturnType ReturnType;
virtual ReturnType apply (TOOL&) = 0;
protected:
virtual ~Visitable () { };
/** @internal used by the DEFINE_VISITABLE macro.
/// @note may differ from TOOL
typedef typename TOOL::ToolBase ToolBase;
typedef typename TOOL::ReturnType ReturnType;
/** @internal used by the DEFINE_PROCESSABLE_BY macro.
* Dispatches to the actual operation on the
* "visiting tool" (acyclic visitor implementation)
* "visiting tool" (visitor implementation)
* Note: creates a context templated on concrete TAR.
*/
template <class TAR>
static ReturnType dispatchOp(TAR& target, TOOL& tool)
static inline ReturnType
dispatchOp (TAR& target, TOOL& tool)
{
return Dispatcher<TAR,TOOL>::call (tool, target);
return Dispatcher<TAR,ToolBase>::instance().forwardCall (target,tool);
}
public:
/** to be defined by the DEFINE_PROCESSABLE_BY macro
* in all classes wanting to be treated by some tool */
virtual ReturnType apply (TOOL&) = 0;
};
/** mark a Visitable subclass as actually treatable
* by some "visiting tool". Defines the apply-function,
* which is the actual access point to invoke the visiting
@ -268,33 +349,34 @@ namespace cinelerra
virtual ReturnType apply (TOOL& tool) \
{ return dispatchOp (*this, tool); }
// =============================================================(End) Library ====
// ================================================================== Library ====
namespace test
{
typedef Tool<void> VisitingTool;
class HomoSapiens : public Visitable<>
{
public:
DEFINE_PROCESSABLE_BY (Tool);
DEFINE_PROCESSABLE_BY (VisitingTool);
};
class Boss : public HomoSapiens
{
public:
DEFINE_PROCESSABLE_BY (Tool);
DEFINE_PROCESSABLE_BY (VisitingTool);
};
class BigBoss : public Boss
{
public:
DEFINE_PROCESSABLE_BY (Tool);
DEFINE_PROCESSABLE_BY (VisitingTool);
};
class Leader : public Boss
@ -305,9 +387,9 @@ namespace cinelerra
{
};
class VerboseVisitor
: public Tool
: public VisitingTool
{
protected:
void talk_to (string guy)
@ -315,15 +397,18 @@ namespace cinelerra
cout << format ("Hello %s, nice to meet you...\n") % guy;
}
};
class Babbler
: public VerboseVisitor,
public Applicable<Boss>,
public Applicable<BigBoss>
: public Applicable<Boss,Babbler>,
public Applicable<BigBoss,Babbler>,
public Applicable<Visionary,Babbler>,
public ToolType<Babbler, VerboseVisitor>
{
public:
void treat (Boss&) { talk_to("Boss"); }
void treat (BigBoss&) { talk_to("big Boss"); }
void treat (BigBoss&) { talk_to("Big Boss"); }
};
@ -362,7 +447,7 @@ namespace cinelerra
cout << "=== Babbler meets Boss and BigBoss ===\n";
Babbler bab;
Visitor& vista (bab);
VisitingTool& vista (bab);
homo1.apply (vista);
homo2.apply (vista);
}
@ -377,8 +462,8 @@ namespace cinelerra
cout << "=== Babbler meets HomoSapiens and Visionary ===\n";
Babbler bab;
Visitor& vista (bab);
homo1.apply (vista); // doesn't visit HomoSapiens
VisitingTool& vista (bab);
homo1.apply (vista); // error handler (not Applicable to HomoSapiens)
homo2.apply (vista); // treats Visionary as Boss
}

View file

@ -751,16 +751,16 @@ As the builder has to create a render node network implementing most of the feat
* any non-trivial wiring of render nodes, tracks, ports and automation is done by the services of the [[connection manager|ConManager]]
</pre>
</div>
<div title="BuilderStructures" modifier="Ichthyostega" modified="200712090636" created="200706250734" tags="overview design Builder" changecount="17">
<div title="BuilderStructures" modifier="Ichthyostega" modified="200801031929" created="200706250734" tags="overview design Builder" changecount="18">
<pre>* the MObjects implement //Buildable//
* each Buildable can &quot;recieve&quot; a Tool object and aply it
* each Buildable can &quot;receive&quot; a Tool object and apply it
* the different Tool objects are iterated/mapped onto the list of MObjects in the [[Timeline]]
* __Rationale__
** the MObject class hierarchy is rather fixed (it is unlikely the we will be adding much new MObject subclasses)
** so this design makes it easy to add new Tool subclasses, and within each Tool subclass, all operations on the different MObject classes are grouped together, so it is easy to see what is going on.
** a given Tool instance can carry state while beeing iterated, so we don't need any global (or object-global) variables to hold the result of the build process
** a given Tool instance can carry state while being iterated, so we don't need any global (or object-global) variables to hold the result of the build process
This programming technique is often refered to as [[&quot;double dispatch&quot; or &quot;visitor&quot;|VisitorUse]]. We use a special implementation variant of this pattern, known as &quot;acyclic visitor&quot;. This technique was first invented by Robert Martin (1996) &amp;mdash; our code is heavily inspired by the [[Loki library|http://loki-lib.sourceforge.net/]]. We use this approach not only for the builder, but also for carrying out operations on the objects in the EDL in a typesafe manner.
This programming technique is often referred to as [[&quot;double dispatch&quot; or &quot;visitor&quot;|VisitorUse]]. We use a specialized library implementation of this pattern &amp;mdash; heavily inspired by the [[Loki library|http://loki-lib.sourceforge.net/]]. We use this approach not only for the builder, but also for carrying out operations on the objects in the EDL in a typesafe manner.
For the actual building operations see BasicBuildingOperations {{red{TODO:flesh out the actual Operations}}}
@ -3971,12 +3971,43 @@ Using transitions is a very basic task and thus needs viable support by the GUI.
Because of this experience, ichthyo wants to support a more general case of transitions, which have N output connections, behave similar to their &quot;simple&quot; counterpart, but leave out the mixing step. As a plus, such transitions can be inserted at the source ports of N clips or between any intermediary or final output ports as well. Any transition processor capable of handling this situation should provide some flag, in order to decide if he can be placed in such a manner. (wichin the builder, encountering a inconsistently placed transition is just an [[building error|BuildingError]])
</pre>
</div>
<div title="VisitorUse" modifier="Ichthyostega" modified="200711280312" created="200711280302" tags="impl discuss" changecount="11">
<pre>Using the ''Visitor Design Pattern'' is somewhat controversial, mainly because this pattern is rather complicated, requires certain circumstances to be usefull, and &amp;mdash; especially when used in the original form described by Gamma et al &amp;mdash; puts quite some burden on the implementor. This problems can be ameliorated by using library implementation techniques described by Robert Martin and implemented e.g. by Alexandrescu in the Loki library.
<div title="VisitingToolImpl" modifier="Ichthyostega" modified="200801040040" created="200801032003" tags="impl" changecount="8">
<pre>The ''Visitor Pattern'' is a special form of //double dispatch// &amp;mdash; selecting the function actually to be executed at runtime based both on the concrete type of some tool object //and// the target this tool is applied to. The rationale is to separate some specific implementation details from the basic infrastructure and the global interfaces, which can be limited to describe the fundamental properties and operations, while all details relevant only for some specific problem can be kept together encapsulated in some tool implementation class. Typically, there is some iteration mechanism, allowing to apply these tools to all objects in a given container, collection or object graph, without knowing the exact type of the target and tool objects.
!Problems with Visitor
The visitor pattern is not very popular, because any implementation is tricky, difficult to understand and often puts quite some burden on the user code. Even Erich Gamma says that on his list of bottom-ten patterns, Visitor is at the very bottom. This may be due to the fact that this original visitor implementation (often called the ''~GoF visitor'') causes a cyclic dependency between the target objects and the visiting tool objects, and includes some repetitive code (which results in silent failure if forgotten). Robert Martin invented 1996 an interesting variation (commonly labled ''acyclic visitor''). By using a marker base class for each concrete target type the visitor is able to treat and by applying a dynamic cast, we can get rid of the cyclic dependencies. Besides the runntime cost of a dynamic cast, the catch is that it puts still more burden on the user code, because now we need to maintain consistently two parallel object hierarchies.
There seemd not to be much room for improvement, at least before the advent of generic programming and the discovery of template metaprogramming. ''Loki'' (Alexandrescu, 2000) shows us how to write a library implementation which hides away the technicallities of the visitor pattern and automatically generates most of the repetitive code.
!Requirements
* cyclic dependencies should be avoided or at least restricted to some library related place.
* The responsibilities for user code should be as small as possible. Especially, we should minimize the necessity to do corresponding adjustments to separate code locations, e.g. the necessity to maintain parallel hierarchies.
* Visitor is about //double dispatch,// thus we can't avoid using some table lookup implementation, and we can't avoid using some of the cooperating classes vtables. We can expect at least two table lookups for each call dispatch. Besides that, the implementation should not be too wasteful...
* individual &quot;visiting tool&quot; implementation classes should be able to opt in or opt out on implementing functions treating some of the visitable subclasses.
* there should be a safe fallback mechanism backed by the visitable object's hierarchy relations. If some concrete visiting tool class decides not to implement a {{{treat(..)}}}-function for some concrete target type, the call should fall back to the next best match according to the target object's hierarchy, i.e. the next best {{{treat(..)}}}-function should be used.
The last requirement practically rules out the Loki acyclic visitor, because this implementation calls a general fallback function when an exact match based on the target object's type is not possible. Considering our use of the visitor pattern within the render engine builder, such a solution would not be of much use: Some specific builder tool may implement a {{{treat(CompoundClip&amp;)}}}-function, while most of the other builder tools just implement a {{{treat(Clip&amp;)}}}-function, thus handling any multichannel clip via the general clip interface. This is exactly the reason why we want to use visitor at first place. Isolate specific treatment, implement against interfaces.
!Implementation Notes
A good starting point for understanding our library implementation of the visitor pattern is {{{tests/components/common/visitingtoolconcept.cpp}}}, which contains a all-in-one-file proof of concept implementation, on which the real implementation ({{{&quot;common/visitor.hpp&quot;}}}) was based.
* similar to Loki, we use a {{{Visitable}}} base class and a {{{Tool}}} base class (we prefer the name &quot;Tool&quot; over &quot;Visitor&quot;, because it makes the intended use more clear).
* the objects in the {{{Visitable}}} hierarchy implement an {{{apply(Tool&amp;)}}}-function. This function needs to be implemented in a very specific manner, thus the {{{DEFINE_PROCESSABLE_BY}}} macro should be used when possible to insert the definition into a concrete {{{Visitable}}} class.
* similar to the acyclic visitor, the concrete visiting tool classes inherit from {{{Applicable&lt;TARGET, ...&gt;}}} marker base classes, where the template parameter {{{TARGET}}} is the concrete Visitable type this tool wants to treat, either directly by defining a {{{treat(ConcreteVisitable&amp;)}}}, or by falling back to some more general {{{treat(...)}}} function.
* consequently our implementation is //rather not acyclic// &amp;mdash; the concrete tool implementation depends on the full definition (header) of all concrete Visitables, but we avoid cyclic dependencies on the interface level. By using a typelist technique inspired by Loki, we can concentrate these dependencies in one single header file, which keeps things maintainable.
* we use the {{{Applicable&lt;TARGET,TOOLImpl&gt;}}} marker base classes to drive the generation of Dispatcher classes, each of which holds a table of trampoline functions for carrying out the actual double dispatch at call time. Each concrete Visitable using the {{{DEFINE_PROCESSABLE_BY}}}-macro generates a separate Dispatcher table containing slots for each concrete tool implementation class. To store and access the index position for these &quot;call slots&quot;, we use a tag associated with the concrete visiting tool class, which can be retrieved by going though the tool's vtable
* __runtime cost__: the concrete tool's ctor stores the trampoline pointers (this could be optimized to be a &quot;once per class&quot; initialisation). Then, for each call, we have 2 virtual function calls and a lookup and call of the trampoline function, typically resulting in another virtual function call for resolving the {{{treat(..)}}} function on the concrete tool class
* __extension possibilities__: while this system may seem complicated, you should note that it was designed with special focus on extension and implementing against interfaces:
** not every Visitable subclass needs a separate Dispatcher. As a rule of thumb, only when some type needs to be treated separately within some concrete visiting tool (i.e. when there is the need of a {{{treat(MySpecialVisitable&amp;)}}}), then this class should use the {{{DEFINE_PROCESSABLE_BY}}}-macro and thus define it's own {{{apply()}}}-function and Dispatcher. In all other cases, it is sufficient just to extend some existing Visitable, which thus will act as an interface with regards to the visiting tools.
** because the possibility of utilizing virtual {{{treat(...)}}} functions, not every concrete visiting tool class needs to define a set of {{{Applicable&lt;...&gt;}}} base classes (and thus get a separate dispatcher slot). We need such only for each //unique set// of Applicables. All other concrete tools can extend existing tool implementations, sharing and partially extending the same set of virtual {{{treat()}}}-functions.
** when adding a new &quot;first class&quot; Visitable, i.e. a concrete target class that needs to be treated separately in some visiting tool, the user needs to include the {{{DEFINE_PROCESSABLE_BY}}} macro and needs to make sure that all existing &quot;first class&quot; tool implementation classes include the Applicable base class for this new type. In this respect, our implementation is clearly &quot;cyclic&quot;. (Generally speaking, the visitor pattern should not be used when the hierarchy of target objects is frequently extended and remoulded). But, when using the typelist facillity to define the Applicable base classes, we'll have one header file defining these collection of Applicables and thus we just need to add our new concrete Visitable to this header and recompile all tool implementation classes.
** when creating a new &quot;~Visitable-and-Tool&quot; hierarchy, the user should derive (or typedef) and parametrize the {{{Visitable}}}, {{{Tool}}} and {{{Applicable}}} templates, typically into a new namespace.
</pre>
</div>
<div title="VisitorUse" modifier="Ichthyostega" modified="200801040039" created="200711280302" tags="impl discuss" changecount="15">
<pre>Using the ''Visitor Design Pattern'' is somewhat controversial, mainly because this pattern is rather complicated, requires certain circumstances to be usefull, and &amp;mdash; especially when used in the original form described by Gamma et al &amp;mdash; puts quite some burden on the implementor. This problems can be ameliorated by using template based library implementation techniques along the lines of the [[Loki library|http://loki-lib.sourceforge.net/]].
&amp;rarr; [[implementation deatails|VisitingToolImpl]]
!why bothering with visitor?
In the Cinelerra-3 Proc layer, ichthyo uses the visitor pattern to overcome another notorious problem when dealing with more complex class hierarchies: either, the //interface// (root class) is so unspecific to be almost useless, or, in spite of having a useful contract, this contract will be broken by some subclasses. Initially, when designing the classes, the problems aren't there (obviously, because they could be taken as design flaws). But than, under the pressure of real features, new types are added which //need to be in this hierarchy// and at the same time //need to have this and that special behaviour// and here we go ...
Visitor helps us to circumvent this catch: the basic operations can be written against the top level interface, such, that they include visiting some object collection internally. Now, on a case-by-case base, local operations can utilize some sub interface or the given concrete type's public interface.
In the Cinelerra-3 Proc layer, Ichthyo uses the visitor pattern to overcome another notorious problem when dealing with more complex class hierarchies: either, the //interface// (root class) is so unspecific to be almost useless, or, in spite of having a useful contract, this contract will effectively be broken by some subclasses (&quot;elliptical circles&quot;). Initially, when designing the classes, the problems aren't there (obviously, because they could be taken as design flaws). But than, under the pressure of real features, new types are added which //need to be in this hierarchy// and at the same time //need to have this and that special behaviour// and here we go ...
Visitor helps us to circumvent this catch: the basic operations can be written against the top level interface, such, that they include visiting some object collection internally. Now, on a case-by-case base, local operations can utilize some sub interface or the given concrete type's public interface. So visitor helps to encapsulate cooperating specific technical details within some concrete visiting tool implementation, while forcing them to be implemented against some interface or sub-interface of the target objects.
!!well suited for using visitors
generally speaking, visitors are preferable when the underlying element type hierarchy is rather stable, but new operations are to be added frequently.