Re: [acyclic visitor]The "accept" operation of an object
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Sun, 15 May 2005 19:08:56 GMT
Responding to Ram...
I am quite new to the visitor-pattern. I did not understand it completely when I read about it first, but then I have re-invented it, when I needed to interpret a parse-tree in two different ways - and reinventing always helps me to understand. Still some question have arosen:
The first thing to understand is why most of the GoF patterns exist. Most of them address relationships that are too complicated to express as a simple association. The complication is usually dynamic in nature (i.e., who the specific participants are must be determined at run time). In addition, there are usually issues of behavior substitution basecon collaboration context. For most of the GoF patterns the solution is delegation, subclassing, polymorphic substitution, and dynamic relationship instantiation. Thus patterns like Strategy, State, Composite, etc. all kind of look the same superficially because they all have these elements somewhere.
Visitor is somewhat unique in that it deals with a very special problem where the collaborating objects are from subclasses in different trees but they need to access the specializations of the subclass (i.e., there is no behavior substitution). In addition, an object from any subclass in one tree may collaborate with an object of any subclass in the other tree. In effect one has a set of 1:1 relationships between each subclass in one tree with every subclass of the other tree. IOW, one has a combinatorial complete set of all possible 1:1 relationships between subclasses of two trees. Trying to explicitly "hard-wire" each of those relationships can get very messy. Visitor provides an elegant "cookbook" mechanism for implementing those relationships.
My "accept" operation does not return "void" but a type called "Value". Actually the meaning of this accept-operation is the Value of this object as given by the interpreter, which is the parameter (visitor) of this accept operation. Is there anything wrong with "accept" not returning "void" but a "Value"?
Yes, unless you are just using the return to indicate a failure in the acceptance. What Accept is really doing is establishing a temporary 1:1 relationship between two objects from subclasses in different trees. At that point there is no collaboration yet, so there is no reason to return a value.
If you look at the sequence diagram on pg. 335, the collaboration actually takes place between aConcreteVisitor and either aConcreteElementA or aConcreteElementB when the call to OperationA() or OperationB(), respectively, is made. That is when the relationship assigned by invoking Accept() is actually navigated for the collaboration. So if a value needs to be returned from the collaboration, then it should be OperationA() or OperationB() that does so.
I also would like to implement the acyclic visitor. A page on the web gives IIRC an implementation in Java similar to this:
void accept( Visitor visitor ) { if( visitor instanceof MyVisitor ) { MyVisitor myVisitor =( MyVisitor )visitor; visitor.visit( this ); }}
The "instanceof" will prevent a cast-exception. I am not sure if I understand the situations which might lead to this situation, but I would like not to silently ignore such a call, but to get an exception indeed, so I would like to remove the test to get:
What this code seems to be doing is dealing with the situation where a object may not have collaborations with objects from all of the subclasses of the other tree. (I am not keen on that usage but since it is for other reasons unrelated to Visitor, let's not go there.)
If you want to respond to that situation with this code, you just need an else clause to throw the exception.
void accept( Visitor visitor ) { MyVisitor myVisitor =( MyVisitor )visitor; visitor.visit( this ); }
Because I do not return "void" I also would have to return some dummy value in the case of a false value of the expression "visitor instanceof MyVisitor", and it is not obvious what such a value should be.
Another reason for not returning a value. B-)
Another question: I am creating a library for some container objects. One container class is called a "Room", for example. Even if such need never arose until today, I am thinking about inserting an "accept"-operation into all these classes by way of precaution for the clients. This might look like:
interface Visitor {}; // provided by my library once for all uses interface Value {}; // provided by my library once for all uses interface RoomVisitor { visit( Room ); }; class Room { ... public Value accept( Visitor visitor ) ... }
Anything wrong with this idea? (Inserting an "accept" operation into many classes just for the case clients might need it, even if a situation, where it is needed, has not appeared yet?)
Yes. Solve the problem in hand, not problems that might be. The reason one uses the OO approach is so that when the world does change one can deal with the change fairly easily. That precludes the need to anticipate specific future changes. Visitor involves significant infrastructure and some indirection overhead. Why undertake that when it is not needed?
************* There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@xxxxxxxxxxxxxxxxx Pathfinder Solutions -- Put MDA to Work http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman (888)OOA-PATH
.
- Prev by Date: Re: Interdependencies
- Next by Date: Re: When A Have a Collections of B's -- a Design Question
- Previous by thread: Code generation API's anywhere ?
- Next by thread: Visio UML
- Index(es):
Relevant Pages
|