At this point, you could follow the same approach that was used for double dispatching and create new subtypes of Aluminum, Paper, Glass, andCardboard that implement the accept( ) method. For example, the new Visitable Aluminum would look like this:
# PatternRefactoring/trashvisitor/VAluminum.py # Taking the previous approach of creating a # specialized Aluminum for the visitor pattern. class VAluminum(Aluminum, Visitable): def __init__(self, wt): Aluminum.__init__(wt) def accept(self, v): v.visit(self)
However, we seem to be encountering an “explosion of interfaces:” basic Trash, special versions for double dispatching, and now more special versions for visitor. Of course, this “explosion of interfaces” is arbitrary-one could simply put the additional methods in the Trash class. If we ignore that we can instead see an opportunity to use the Decorator pattern: it seems like it should be possible to create a Decorator that can be wrapped around an ordinary Trash object and will produce the same interface as Trash and add the extra accept( ) method. In fact, it’s a perfect example of the value of Decorator.
The double dispatch creates a problem, however. Since it relies on overloading of both accept( ) and visit( ), it would seem to require specialized code for each different version of the accept( ) method. With C++ templates, this would be fairly easy to accomplish (since templates automatically generate type-specialized code) but Python has no such mechanism-at least it does not appear to. However, reflection allows you to determine type information at run time, and it turns out to solve many problems that would seem to require templates (albeit not as simply). Here’s the decorator that does the trick :
# PatternRefactoring/trashvisitor/VisitableDecorator.py # A decorator that adapts the generic Trash # classes to the visitor pattern. class VisitableDecorator(Trash, Visitable): def __init__(self, t): self.delegate = t try: self.dispatch = Visitor.class.getMethod ( "visit", Class: t.getClass() ) except ex: ex.printStackTrace() def getValue(self): return delegate.getValue() def getWeight(self): return delegate.getWeight() def accept(self, Visitor v): self.dispatch.invoke(v, delegate)
[[ Description of Reflection use ]]
The only other tool we need is a new type of Fillable adapter that automatically decorates the objects as they are being created from the originalTrash.dat file. But this might as well be a decorator itself, decorating any kind of Fillable:
# PatternRefactoring/trashvisitor/FillableVisitor.py # Adapter Decorator that adds the visitable # decorator as the Trash objects are # being created. class FillableVisitor(Fillable): def __init__(self, ff): self.f = ff def addTrash(self, t): self.f.addTrash(VisitableDecorator(t))
Now you can wrap it around any kind of existing Fillable, or any new ones that haven’t yet been created.
The rest of the program creates specific Visitor types and sends them through a single list of Trash objects:
# PatternRefactoring/trashvisitor/TrashVisitor.py # The "visitor" pattern with VisitableDecorators. # Specific group of algorithms packaged # in each implementation of Visitor: class PriceVisitor(Visitor): alSum = 0.0 # Aluminum pSum = 0.0 # Paper gSum = 0.0 # Glass cSum = 0.0 # Cardboard def visit(self, al): v = al.getWeight() * al.getValue() print("value of Aluminum= " + v) alSum += v def visit(self, p): v = p.getWeight() * p.getValue() print("value of Paper= " + v) pSum += v def visit(self, g): v = g.getWeight() * g.getValue() print("value of Glass= " + v) gSum += v def visit(self, c): v = c.getWeight() * c.getValue() print("value of Cardboard = " + v) cSum += v def total(self): print ( "Total Aluminum: $" + alSum + "\n Total Paper: $" + pSum + "\nTotal Glass: $" + gSum + "\nTotal Cardboard: $" + cSum + "\nTotal: $" + (alSum + pSum + gSum + cSum)) class WeightVisitor(Visitor): alSum = 0.0 # Aluminum pSum = 0.0 # Paper gSum = 0.0 # Glass cSum = 0.0 # Cardboard def visit(self, Aluminum al): alSum += al.getWeight() print ("weight of Aluminum = " + al.getWeight()) def visit(self, Paper p): pSum += p.getWeight() print ("weight of Paper = " + p.getWeight()) def visit(self, Glass g): gSum += g.getWeight() print ("weight of Glass = " + g.getWeight()) def visit(self, Cardboard c): cSum += c.getWeight() print ("weight of Cardboard = " + c.getWeight()) def total(self): print ( "Total weight Aluminum: " + alSum + "\nTotal weight Paper: " + pSum + "\nTotal weight Glass: " + gSum + "\nTotal weight Cardboard: " + cSum + "\nTotal weight: " + (alSum + pSum + gSum + cSum)) class TrashVisitor(UnitTest): Bin = ArrayList() PriceVisitor pv = PriceVisitor() WeightVisitor wv = WeightVisitor() def __init__(self): ParseTrash.fillBin("../trash/Trash.dat", FillableVisitor( FillableCollection(bin))) def test(self): Iterator it = bin.iterator() while(it.hasNext()): Visitable v = (Visitable)it.next() v.accept(pv) v.accept(wv) pv.total() wv.total() def main(self, String args): TrashVisitor().test() In **Test( )**, note how visitability is added by simply creating a different kind of bin using the decorator. Also notice that the **FillableCollection** adapter has the appearance of being used as a decorator (for **ArrayList**) in this situation. However, it completely changes the interface of the **ArrayList**, whereas the definition of *Decorator* is that the interface of the decorated class must still be there after decoration.
Note that the shape of the client code (shown in the Test class) has changed again, from the original approaches to the problem. Now there’s only a single Trash bin. The two Visitor objects are accepted into every element in the sequence, and they perform their operations. The visitors keep their own internal data to tally the total weights and prices.
Finally, there’s no run time type identification other than the inevitable cast to Trash when pulling things out of the sequence. This, too, could be eliminated with the implementation of parameterized types in Java.
One way you can distinguish this solution from the double dispatching solution described previously is to note that, in the double dispatching solution, only one of the overloaded methods, add( ), was overridden when each subclass was created, while here each one of the overloaded visit( ) methods is overridden in every subclass of Visitor.