Unit 28 Visitor
description
Transcript of Unit 28 Visitor
1
Unit 28Visitor
Summary prepared by Kirk Scott
2
3
4
5
6
7
8
Design Patterns in JavaChapter 29
Visitor
Summary prepared by Kirk Scott
9
The Introduction Before the Introduction
• My presentation of the visitor pattern will follow this outline:
• The book presents the Visitor design pattern by using it to extend code which is based on the Composite design pattern
• I will follow this example in order to define the pattern
10
• I will then try to illustrate the pattern with an example simpler than the book’s
• In my example the Visitor pattern will be applied to a leaf of a Component only
• I will not give complete code for an implementation of the pattern for a Composite
11
• I will then return to the book’s discussion of an important syntactical point that becomes apparent with this pattern
• I will then examine the potential shortcomings of the pattern
12
• The posted “assignment” for this unit deals with an implementation of the pattern for a Composite even though I don’t pursue that in depth in these overheads
• It should be within your ability to apply the pattern in that context, using the book and these overheads as a guide
• Such a question could appear on the last test
13
Starting the Book Material on the Pattern
• Assuming that you have access to the code for a class, extending the class would mean adding methods to it, for example
• If you, as a client developer, cannot change the code of a given class, the class developer can make it possible for you to add functionality to the class
14
• The developer of the service class defines a visitor interface containing visit() methods
• The developer of the service class defines a an accept() method in the service classes
• The accept() method accepts an instance of a visitor as an explicit parameter
• Inside the accept() method, visit() is called on the service object that accept() was called on
15
• The client developer creates concrete classes that implement the visitor interface
• These classes contain concrete implementations of the visit() method
• It is these concrete visit() methods that the service code accept() method calls on itself
16
• The client code creates a service object, creates a visitor object, and calls accept() on the service object, passing the visitor in
• Note how passing objects that contain desired functionality is reminiscent of the Command design pattern
17
Book Definition of Pattern
• Book definition:• The intent of Visitor is to let you define a new
operation for a hierarchy without changing the hierarchy classes.
18
Visitor Mechanics
• The visitor pattern is based on some standard mechanics
• These mechanics potentially support an unlimited variety of extensions (new methods) that can be applied to service classes by clients
• The needed mechanics fall into three parts:
19
The Visitor Interface
• 1. There is a visitor interface that is associated with the set of service classes
• This visitor interface is defined to have one or more methods named visit()
• There will be a different version of the visit() method for each service class
• Each visit() method takes as its explicit parameter an instance of the service class that it applies to
20
The Concrete Client Classes that Implement the Visitor Interface
• 2. The client side will develop concrete classes that implement the interface
• The concrete classes implement the versions of the visit() method for the service classes
• These concrete implementations of the method embody the new functionality that is to be added to/passed to the hierarchy of classes
• Keep in mind that the visit() methods take in service class objects as their explicit parameters
21
The accept() Method in the Service Classes
• 3. Each class in the service hierarchy has to have an accept() method
• The accept() method takes an explicit parameter that is typed to the visitor interface
• When the accept() method is called on a service class object, a visitor object is passed in
22
• Inside the accept() method, the visit() method is called on the explicit parameter that was passed in
• The explicit parameter that is sent to the visit() method is the service class object object that accept() was called on
23
An Example with a Composite
• The UML diagram given on the overhead following the next one shows the MachineComponent composite hierarchy with visitation added
• The MachineComponent composite hierarchy is the service class hierarchy
• MachineComponent has an (abstract) accept() method
• Machine and MachineComposite have concrete accept() methods
24
• The UML diagram also shows the MachineVisitor interface
• This interface contains two visit() methods• One visit() method takes a Machine object as
its explicit parameter• The other visit() method takes a
MachineComposite object as its explicit parameter
25
26
Fleshing Out MachineVisitor
• If client code is going to make use of visitation, it has to create concrete visitor classes
• These concrete visitor classes contain concrete implementations of the visit() methods
• The concrete visit() methods contain the functionality that is being “added to” the existing service hierarchy
• The functionality being added in this example is the ability to “find” a component in a composite
27
• The UML diagram on the overhead following the next one shows a concrete class, FindVisitor
• It implements the MachinedVisitor interface• An instance of FindVisitor would be the object
passed as an explicit parameter when calling the accept() method on a Machine or MachineComposite object
28
• FindVisitor has two versions of visit()• One version takes an instance of Machine as its
explicit parameter• The other takes an instance of
MachineComposite as its explicit parameter• FindVisitor also has an application specific
method, find()• How find() works depends on how visit() is coded
29
30
Another Example
• In order to follow the book’s example you would have to follow the logic of traversing the composite and implementing the logic of finding
• I don’t want to deal with those complexities• Therefore, the “other” example comes right
away• This is the example I want to pursue
31
• The logic of this example is obtaining the name of a machine in reverse order
• This is simpler than implementing the logic of finding in a composite
• Also, I only do the implementation for a leaf• That way I avoid even having to consider
traversing the composite• The focus is on the Visitor pattern, not on the
complexities of the Composite pattern
32
Machine Names
• Suppose that the Machine class has a String instance variable name and a getName() method
• Suppose that the class doesn’t have a getNameReversed() method, which would return the String name in reverse order, but this functionality is desired
33
• Suppose that you don’t have access to the Machine class in order to implement this
• Suppose also that this is something that you might want to do more than once
• You don’t just want to call getName() in client code and then reverse the name every time the need arises in client code
• Instead, you’d like to package up the functionality in a visitor
34
• Code will be given for a NameReversedVisitor class for getting the names of machine components in reversed order
• The class will contain two visit() methods, one for Machine and one for MachineComposite
• These methods actually contain the logic for reversing names
• Only the code for the Machine version will be given
35
The Calling Sequence
• The NameReversedVisitor class will contain a descriptively named method, getNameReversed()
• Client code will construct an instance of NameReversedVisitor
• Client code will call getNameReversed() on that visitor
• Client code will pass a MachineComponent object as an explicit parameter to getNameReversed()
36
• getNameReversed() calls accept() on the MachineComponent parameter (in the leaf case, a simple Machine)
• The version of accept() for a Machine will be called
• The accept() method calls visit() on the visitor and passes in “this” the machine that accept() was called on, as an implicit parameter
37
• This verbal description is kind of hopelessly twisted
• Hopefully, it will be possible to follow the code• The code for the NameReversedVisitor class is
given on the next overhead• Remember that an implementation of visit() is
only given for the Machine class
38
• public class NameReversedVisitor implements MachineVisitor• {• public String getNameReversed(MachineComponent mc)• {• mc.accept(this);• }• public String visit(Machine m)• {• String name = m.getName();• String retString = "";• for(int i = name.length() - 1; i >= 0; i--)• {• retString = retString + name.substring(i, i + 1);• }• return retString;• }• public String visit(MachineComposite machineCompositeIn)• {• // Some other implementation.• }• }
39
• This is what accept() looks like:
• public void accept(MachineVisitor v)• {• v.visit(this);• }
40
The Calling Sequence, Repeated
• The code execution sequence would go like this:• Client code constructs a machine visitor and a
machine• It calls the getNameReversed() method on the
visitor, passing in the machine• The implementation of getNameReversed()
consists of a call to the accept() method on the machine, passing in the visitor
41
• The implementation of accept() consists of a call to the visit() method on the visitor, passing in the machine
• The visit() method contains logic that gets the name of the machine and returns a string containing the name in reversed order
42
• I have provided no separate UML diagram for the other example
• It would be just like the book’s UML diagrams with the FindVisitor replaced by the ReversedNameVisitor
• The book’s example is going to be pursued in greater detail next, so the diagrams aren’t repeated here
• They will be shown again shortly
43
A Critical Aspect of the Implementation of the Pattern
• This design pattern illustrates a subtle aspect of Java syntax that hasn’t come up before
• It has to do with where methods are declared and defined, and whether or not it is practical to inherit them
• It has specifically to do with the declaration and implementation of the visit() method and the call to visit() in the accept() method
44
The MachineVisitor Interface
• The UML diagram for the Machine Component hierarchy and the MachineVisitor interface is shown again on the following overhead
• There are separate visit() methods, one that takes a Machine as its parameter, another that takes a MachineComposite as its parameter
• The visit() method declarations in the interface are shown on the overhead following the next one
45
46
• public interface MachineVisitor• {• void visit(Machine m);• void visit(MachineComposite mc);• }
47
The accept() Method in the Service Classes (Composite Hierarchy)
• This was breezed over before, but the UML diagram has a mistake in it
• The accept(:MachineVisitor) method in the MachineComponent class should have been in italics, because this is the abstract method
• Its implementation is forced into the concrete subclasses Machine and MachineComposite
48
• Now the book makes the following observation:• In each of the subclasses, Machine and
MachineComposite, the implementation of the accept() method is going to look exactly the same:
• public void accept(MachineVisitor v)• {• v.visit(this);• }
49
• When you see this, you might think that the implementation of accept() can be pushed into the superclass (as implied by the mistake in the UML diagram)
• In other words, you might think that there is no need for it to be abstract in the MachineComponent class
• This is not the case
50
• The compiler will see a difference between the implementations of the methods in the superclass and the two subclasses
• It all depends on the meaning of the term “this”, depending on the context in which it appears
51
• Challenge 29.1• “What difference will a Java compiler see
between the accept() methods in the Machine and MachineComposite() classes?
• (Don’t peek on this one unless you have to; it’s key to understanding Visitor.)”
52
• Solution 29.1• “The difference is in the type of the this object. • The accept() method calls the visit() method of a
MachineVisitor object. • The accept() method in the Machine class will look
up a visit() method with the signature visit(:Machine), whereas the accept() method in the MachineComposite class will look up a method with the signature visit(:MachineComposite).”
53
• Comment mode on:• By studying the given code closely, it is possible to
answer the challenge correctly even without fully grasping what it means
• “this” is the only thing that could change between the two implementations of the method
• To tell the truth, at first glance I wasn’t completely convinced, so I looked into the alternative that the book rejected
54
• What would happen if you did implement the accept() method in MachineComponent and let the two subclasses inherit it?
• Not only will the compiler see the difference in the correct solution
• It will tell you something is wrong with the incorrect solution
55
• This is what’s in the MachineVisitor interface:
• public interface MachineVisitor• {• void visit(Machine m);• void visit(MachineComposite mc);• }
56
• The accept() method would look exactly the same if you defined it in the abstract MachineComponent superclass and the concrete subclasses inherited it:
• public void accept(MachineVisitor v)• {• v.visit(this);• }
57
What’s Wrong
• The compiler will tell you that it can’t find a version of the visit() method that takes an instance of MachineComponent as the explicit parameter
• In the MachineVisitor class there are only versions of visit() that take an instance of Machine or an instance of MachineComposite
58
An Elaboration of What’s Wrong
• We have seen cases in the past where you can call a method with an explicit parameter typed to a superclass, and it’s OK to pass in an actual parameter object that is of a subclass
• In a sense, this is the reverse of that situation• You are trying to call a method with an explicit
parameter of a superclass type, when the only implementations of the method have subclass formal parameters
59
• You can’t pass a superclass parameter to something that is typed to accept a subclass
• That is, you can’t have a subclass reference to a superclass type, (unless it’s a situation where you can check the reference type with instanceof and recover the subclass reference by casting)
60
• Consider this “reverse” case of superclass/subclass parameter passing again
• It’s really a question about overloading• You have various versions of the method that
are distinguished by their parameter types
61
• Syntactically, the problem is that there isn’t a version with a superclass parameter
• Logically, there shouldn’t be one—so all is well• This just means that you have to push the
implementations into the subclasses, as shown
62
Potential Shortcomings of the Pattern
• This design pattern has certain shortcomings• The UML diagram showing a concrete visitor
class is repeated on the following overhead for reference
63
64
• This is the motivating idea:• The machine composite hierarchy doesn’t
implement a find() method• The desire arises to have one, without
implementing it in the machine composite hierarchy
• This can be done by means of a visitor
65
• The first thing you might observe about the pattern is that it’s kind of round-about
• The visit() and accept() methods are essentially intertwined
66
• The service classes have accept() methods• Those accept() methods take instances of visitor
objects• The visitor objects have versions of the visit()
method with a parameter for each of the service classes
• For a given service object, inside the body of accept(), visit() is called on the visitor, passing in “this” as the explicit parameter
67
• This intertwinement is unavoidable when you are trying to meet these conditions:
• You can’t change the service code class• You still want to package up the functionality
so that it can be used more than once
68
Is This a Good Idea?
• The next consideration is whether this is a good idea at all
• In order to implement find() in the book’s example it is necessary to traverse a composite data structure using visit()
• The underlying operation is to find a given node in the structure, but different versions of visit() are needed for machines and machine composites
69
• This example illustrates the fundamental shortcoming of the design pattern
• As an outsider, without access to the service class’s internals, how do you know how to correctly traverse the structures?
• Do you know, for example, whether cycles are allowed, and whether you’ll have to handle them?
• Remember the discussion of isTree() when presenting the Composite pattern
70
• If traversal is so important, why wasn’t it built into the class?
• If there are potential pitfalls, shouldn’t the code be implemented in the service class and not externally?
• Why can’t it be added to the class now?• Why has the correct handling of a potentially
problematic situation suddenly become the responsibility of someone who is writing client code?
71
• There are no ultimate answers to these questions
• Some people think the pattern is useful• Others think it’s not• In the long run, I think it’s worth covering not
because of its intent, but because of the point made at the beginning about Java syntax and compilation
72
• It is important to keep careful track of how the compiler tells things apart
• This can affect where in an inheritance hierarchy you implement methods that look the same
• It was not a trick, but really, a special case• The methods looked the same because they both
referred to “this”, but “this” in two different classes will be two different kinds of objects
73
Lasater’s UML
• Lasater’s diagram, this time, does not seem as informative as the book’s diagrams
• It includes a client, but it shows closed arrowheads from client to visitor and from client to object structure to element
• I’m pretty sure he was daydreaming and these should be open arrowheads
• It is apparent that there are accept() and visit() methods, but otherwise the diagram is not very helpful
74
75
One Last Remark at the End of the Composite Based Patterns—Don’t Skip This
• The visitor pattern is the last of the patterns related to composite as presented in the overheads for the units in the course
• This is just a last remark having to do with composites
• You may recall the unit on the Iterator pattern
76
• The book chapter on the Iterator came after the chapter on composites
• This meant that the book could cover iteration over a composite and I did not do so in the unit on iteration
• In theory it would be possible to have one last section, in order to cover iteration over a composite now
77
• This would make for a pretty comprehensive treatment of composites
• I will not cover it in these overheads and there will be no extra unit on this
• The main point of mentioning this is that you should still have some grasp of iteration when taking the last test
• There is a possibility that iteration will come again in the context of composites
78
The End