SpecialistOff.NET / Вопросы / Статьи / Фрагменты кода / Резюме / Метки / Помощь / Файлы
НазадМетки: python
Remember that polymorphism can occur only via method calls, so if you want double dispatching to occur, there must be two method calls: one used to determine the type within each hierarchy. In the Trash hierarchy there will be a new method called addToBin( ), which takes an argument of an array of TypedBin. It uses this array to step through and try to add itself to the appropriate bin, and this is where you’ll see the double dispatch.
The new hierarchy is TypedBin, and it contains its own method called add( ) that is also used polymorphically. But here’s an additional twist: add( ) is overloaded to take arguments of the different types of trash. So an essential part of the double dispatching scheme also involves overloading.
Redesigning the program produces a dilemma: it’s now necessary for the base class Trash to contain an addToBin( ) method. One approach is to copy all of the code and change the base class. Another approach, which you can take when you don’t have control of the source code, is to put the addToBin( ) method into an interface, leave Trash alone, and inherit new specific types of Aluminum, Paper, Glass, and Cardboard. This is the approach that will be taken here.
Most of the classes in this design must be public, so they are placed in their own files. Here’s the interface:
# PatternRefactoring/doubledispatch/TypedBinMember.py # An class for adding the double # dispatching method to the trash hierarchy # without modifying the original hierarchy. class TypedBinMember: # The method: boolean addToBin(TypedBin[] tb)
In each particular subtype of Aluminum, Paper, Glass, and Cardboard, the addToBin( ) method in the interface TypedBinMember is implemented, but it looks like the code is exactly the same in each case:
# PatternRefactoring/doubledispatch/DDAluminum.py # Aluminum for double dispatching. class DDAluminum(Aluminum, TypedBinMember): def __init__(self, wt): Aluminum.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)): return True return False:: # PatternRefactoring/doubledispatch/DDPaper.py # Paper for double dispatching. class DDPaper(Paper, TypedBinMember): def __init__(self, wt): Paper.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False:: # PatternRefactoring/doubledispatch/DDGlass.py # Glass for double dispatching. class DDGlass(Glass, TypedBinMember): def __init__(self, wt): Glass.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False:: # PatternRefactoring/doubledispatch/DDCardboard.py # Cardboard for double dispatching. class DDCardboard(Cardboard, TypedBinMember): def __init__(self, wt): Cardboard.__init__(wt) def addToBin(self, TypedBin[] tb): for(int i = 0 i < tb.length i++) if(tb[i].add(self)) return True return False
The code in each addToBin( ) calls add( ) for each TypedBin object in the array. But notice the argument: this. The type of this is different for each subclass of Trash, so the code is different. (Although this code will benefit if a parameterized type mechanism is ever added to Java.) So this is the first part of the double dispatch, because once you’re inside this method you know you’re Aluminum, or Paper, etc. During the call to add( ), this information is passed via the type of this. The compiler resolves the call to the proper overloaded version of add( ). But since tb[i] produces a reference to the base type TypedBin, this call will end up calling a different method depending on the type of TypedBin that’s currently selected. That is the second dispatch.
Here’s the base class for TypedBin:
# PatternRefactoring/doubledispatch/TypedBin.py # A container for the second dispatch. class TypedBin: c = ArrayList() def addIt(self, Trash t): c.add(t) return True def iterator(self): return c.iterator() def add(self, DDAluminum a): return False def add(self, DDPaper a): return False def add(self, DDGlass a): return False def add(self, DDCardboard a): return False
You can see that the overloaded add( ) methods all return false. If the method is not overloaded in a derived class, it will continue to return false, and the caller (addToBin( ), in this case) will assume that the current Trash object has not been added successfully to a container, and continue searching for the right container.
In each of the subclasses of TypedBin, only one overloaded method is overridden, according to the type of bin that’s being created. For example,CardboardBin overrides add(DDCardboard). The overridden method adds the trash object to its container and returns true, while all the rest of theadd( ) methods in CardboardBin continue to return false, since they haven’t been overridden. This is another case in which a parameterized type mechanism in Java would allow automatic generation of code. (With C++ templates, you wouldn’t have to explicitly write the subclasses or place the addToBin( ) method in Trash.)
Since for this example the trash types have been customized and placed in a different directory, you’ll need a different trash data file to make it work. Here’s a possible DDTrash.dat:
# PatternRefactoring/doubledispatch/DDTrash.dat DDGlass:54 DDPaper:22 DDPaper:11 DDGlass:17 DDAluminum:89 DDPaper:88 DDAluminum:76 DDCardboard:96 DDAluminum:25 DDAluminum:34 DDGlass:11 DDGlass:68 DDGlass:43 DDAluminum:27 DDCardboard:44 DDAluminum:18 DDPaper:91 DDGlass:63 DDGlass:50 DDGlass:80 DDAluminum:81 DDCardboard:12 DDGlass:12 DDGlass:54 DDAluminum:36 DDAluminum:93 DDGlass:93 DDPaper:80 DDGlass:36 DDGlass:12 DDGlass:60 DDPaper:66 DDAluminum:36 DDCardboard:22
Here’s the rest of the program:
# PatternRefactoring/doubledispatch/DoubleDispatch.py # Using multiple dispatching to handle more # than one unknown type during a method call. class AluminumBin(TypedBin): def add(self, DDAluminum a): return addIt(a) class PaperBin(TypedBin): def add(self, DDPaper a): return addIt(a) class GlassBin(TypedBin): def add(self, DDGlass a): return addIt(a) class CardboardBin(TypedBin): def add(self, DDCardboard a): return addIt(a) class TrashBinSet: binSet = [ AluminumBin(), PaperBin(), GlassBin(), CardboardBin() ] def sortIntoBins(self, bin): Iterator e = bin.iterator() while(e.hasNext()): TypedBinMember t = (TypedBinMember)e.next() if(!t.addToBin(binSet)) System.err.println("Couldn't add " + t) def binSet(): return binSet class DoubleDispatch(UnitTest): Bin = ArrayList() TrashBinSet bins = TrashBinSet() def __init__(self): # ParseTrash still works, without changes: ParseTrash.fillBin("DDTrash.dat", bin) def test(self): # Sort from the master bin into # the individually-typed bins: bins.sortIntoBins(bin) TypedBin[] tb = bins.binSet() # Perform sumValue for each bin... for(int i = 0 i < tb.length i++) Trash.sumValue(tb[i].c.iterator()) # ... and for the master bin Trash.sumValue(bin.iterator()) def main(self, String args[]): DoubleDispatch().test()
TrashBinSet encapsulates all of the different types of TypedBins, along with the sortIntoBins( ) method, which is where all the double dispatching takes place. You can see that once the structure is set up, sorting into the various TypedBins is remarkably easy. In addition, the efficiency of two dynamic method calls is probably better than any other way you could sort.
Notice the ease of use of this system in main( ), as well as the complete independence of any specific type information within main( ). All other methods that talk only to the Trash base-class interface will be equally invulnerable to changes in Trash types.
The changes necessary to add a new type are relatively isolated: you modify TypedBin, inherit the new type of Trash with its addToBin( ) method, then inherit a new TypedBin (this is really just a copy and simple edit), and finally add a new type into the aggregate initialization for TrashBinSet.