| 1 | use System.Reflection |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | namespace Cobra.Lang |
|---|
| 5 | |
|---|
| 6 | class Visitor |
|---|
| 7 | is abstract |
|---|
| 8 | """ |
|---|
| 9 | The visitor design pattern is a way of separating an algorithm from an object structure upon |
|---|
| 10 | which it operates. This enables the addition of new operations to existing object structures |
|---|
| 11 | without modifying those structures. [1] |
|---|
| 12 | |
|---|
| 13 | This class implements the visitor pattern, but via reflection so as to not require that the |
|---|
| 14 | classes being visited implement any additional methods for "double dispatch". This approach |
|---|
| 15 | enables less coding and an even greater degree of modularity. It also enables visitation |
|---|
| 16 | directly on final classes, structs, library classes, etc. with no wrapper classes. [2] |
|---|
| 17 | |
|---|
| 18 | In order to leverage this class, you must create a concrete subclass. |
|---|
| 19 | |
|---|
| 20 | Subclasses must override .methodName and then simply implement visitation methods with that |
|---|
| 21 | name and the appropriate parameter types. |
|---|
| 22 | |
|---|
| 23 | When enumerating through subobjects, invoke .dispatch to get type specific dispatch. You can |
|---|
| 24 | also .dispatch on a sequence of objects which will .dispatch on each specific one. |
|---|
| 25 | |
|---|
| 26 | If you add a subclass to the class hierarchy that is being visited then you must either |
|---|
| 27 | (1) remember to implement a method in your visitor class for that specific type, or |
|---|
| 28 | (2) be content when a visitation method for one of its ancestor classes is invoked for it. |
|---|
| 29 | |
|---|
| 30 | If you look at the example much further below, you will get a better idea of what to do. |
|---|
| 31 | Another read of the notes after examining the example could be useful. |
|---|
| 32 | |
|---|
| 33 | Alternatives to the visitor pattern: |
|---|
| 34 | |
|---|
| 35 | * Class extensions. You could put multiple, related class extensions in the same file. |
|---|
| 36 | A pro is that this can make the additional functionality feel like a natural part of the |
|---|
| 37 | classes. A con is that state must be passed from method to method. Also, method signatures |
|---|
| 38 | will have to be updated if additional state is added (unless state is grouped into a |
|---|
| 39 | single context/container object). With the visitor pattern, the state can be stored in the |
|---|
| 40 | instance of the visitor. |
|---|
| 41 | |
|---|
| 42 | * Partial classes. These have the same advantages of class extensions, but without the |
|---|
| 43 | disadvantage of having to pass state around. However, the con is that this is not |
|---|
| 44 | available if the classes are external to your project. For example, if the classes come |
|---|
| 45 | from a library or you are writing a plugin for an application, then partial classes are |
|---|
| 46 | not available. |
|---|
| 47 | |
|---|
| 48 | References: |
|---|
| 49 | |
|---|
| 50 | [1] http://en.wikipedia.org/wiki/Visitor_pattern |
|---|
| 51 | [2] http://www.javaworld.com/javaworld/javatips/jw-javatip98.html |
|---|
| 52 | |
|---|
| 53 | Future: |
|---|
| 54 | |
|---|
| 55 | * Could add a 'strict' mode where if the argument type is not an exact match for the found |
|---|
| 56 | method, an exception is raised. This would help when you don't desire that subclasses are |
|---|
| 57 | silently handled as described above. |
|---|
| 58 | """ |
|---|
| 59 | |
|---|
| 60 | var _willCache = true |
|---|
| 61 | var _methods = Dictionary<of Type, MethodInfo>() |
|---|
| 62 | var _methodName as String |
|---|
| 63 | |
|---|
| 64 | cue init |
|---|
| 65 | base.init |
|---|
| 66 | _methodName = .methodName |
|---|
| 67 | if _methodName[0].isLower |
|---|
| 68 | _methodName = _methodName[0].toUpper.toString + _methodName[1:] |
|---|
| 69 | |
|---|
| 70 | get methodName as String is abstract |
|---|
| 71 | """ |
|---|
| 72 | Returns the method name looked up in .dispatch. |
|---|
| 73 | """ |
|---|
| 74 | # ensure result.length > 0 |
|---|
| 75 | |
|---|
| 76 | def dispatch(obj as Object?) |
|---|
| 77 | """ |
|---|
| 78 | Performs type specific dispatch on the given object. |
|---|
| 79 | A subclass can override this method to change the dispatch logic. |
|---|
| 80 | """ |
|---|
| 81 | if obj is nil, return |
|---|
| 82 | objType = obj.getType |
|---|
| 83 | methInfo as MethodInfo? |
|---|
| 84 | if _willCache, _methods.tryGetValue(objType, out methInfo) |
|---|
| 85 | if methInfo is nil |
|---|
| 86 | methInfo = .getType.getMethod(_methodName, @[objType]) |
|---|
| 87 | if methInfo is nil |
|---|
| 88 | throw InvalidOperationException('Cannot find a method "[_methodName]" with arg type "[objType]"') |
|---|
| 89 | _methods[objType] = methInfo to ! |
|---|
| 90 | methInfo.invoke(this, @[obj]) # would be nice to speed this up |
|---|
| 91 | |
|---|
| 92 | def dispatch(objects as System.Collections.IEnumerable) |
|---|
| 93 | """ |
|---|
| 94 | Performs type specific dispatch on each object in order. |
|---|
| 95 | """ |
|---|
| 96 | for obj in objects, .dispatch(obj) |
|---|