I expect lack of pattern experience will prove problematic while writing Design Patterns in Dart. Of course, that is a big part of why I am writing the book—to earn a better understanding of patterns I do know and to finally understand those with which I have less experience.
Some patterns I don't know because most of my career has been spent in dynamic languages where certain Gang of Four patterns are not really necessary. Other patterns I do not know because I have rarely worked on code that warranted them. Still other patterns I don't know because I hate them.
Tonight's patterns falls into the latter two categories: enter the Visitor Pattern. Why do I hate it if I have not had much call to use it? In my 10+ year of programming, I can count on a single hand the number of times that I have seen it in clients' codebases. Each of the handful of times that I have seen the Visitor Pattern it was somebody writing a pattern for the sake of writing a pattern rather than actual need. That happens a lot, of course, with Design Patterns, but for whatever reason the Visitor Pattern implementations that I have stuck in my mind more than others.
Since I hate it so, it is the perfect behavioral design pattern for me to start investigation. I need to put silly biases behind (and I admit that this bias is very silly) in order to do a proper job in this book. In fact, I need to learn to love each of the patterns for their intrinsic good qualities.
I also need to decide how to approach patterns that are foreign to me. Do I re-implement the examples from the source material (the equipment composite example in GoF is quite approachable) or do I start with an example in the wild? Basing new code from the source material sounds more comfortable and approachable, so I chose the other option tonight. Let's see how I might start by reviewing code in the wild.
And what better project to investigate than Polymer.dart? Specifically, the polymer_expressions package makes good use of Visitor to work with the abstract syntax tree that it builds to process expressions in Polymer code.
The intent of the Visitor patterns is to keep individual elements free of operations that can be better grouped in a single location—concrete implementations of the visitor. A nice example of this is the
Literal
expression class:class Literal<T> extends Expression { final T value; Literal(this.value); accept(Visitor v) => v.visitLiteral(this); String toString() => (value is String) ? '"$value"' : '$value'; bool operator ==(o) => o is Literal<T> && o.value == value; int get hashCode => value.hashCode; }There is not too much to this node in the syntax tree. It might be a string in the Polymer syntax. For example, the empty string in the sample expression from the package page:
{{ person.title + " " + person.getFullName() | upppercase }}This node would be instantiated with
this.value
set to " "
. The result of the toString()
method would return '" "'
. There is even a simple equality check. Nice and simple.There are two visitors in polymer_expressions. Polymer passes each of these visitors to the syntax tree to apply whatever each of these grouped bits of functionality need. The first evals the tree and the second establishes any observers that are necessary:
Object eval(Expression expr, Scope scope) => new EvalVisitor(scope).visit(expr); ExpressionObserver observe(Expression expr, Scope scope) { var observer = new ObserverBuilder().visit(expr); return observer; }The nice thing here is that the individual nodes do not get cluttered with evaluation nor with observers. The node code remains pristine except for the
accept()
method. As each visitor is accepted by the top-level of the AST and sent to each node, the individual nodes call their named methods in the visitor. In the case of LiteralNode
instances, like my empty string, the elements expect the visitor to define visitLiteral()
methods, which is just what polymer_expressions does. For the observer visitor, it establishes a literal observer:class ObserverBuilder extends Visitor { // ... visitLiteral(Literal l) => new LiteralObserver(l); // ... }The other nodes have more complex observer methods, but all of the observers are established in one location—the
ObserverBuilder
—which is much nicer than scattering a bunch of observer methods throughout each of the nodes.I begin to understand the power of this pattern. I don't have any ASTs handy, but I do have a few similar data structures in a few projects that might benefit from this kind of approach. But before I start running about willy-nilly with a new golden hammer, I think it best to see if I can apply this to a simpler data structure first. Tomorrow.
Day #111
You're reminding me of when I extended a ClassFileTransformer to access Java byte code at runtime, and used the ASM library to extend ClassAdapter to inject code into class methods at runtime. They called it Instrumentation, and ASM ClassAdapter, that I chose to use, used the Visitor Pattern to visit the class, and all it's methods at runtime. I just had to override the methods I wanted to use, and inject Java Assembler code at the appropriate points, ie visitMethod(). All just to achieve Lazy Loading of data from a database in a JPA like library, written for an assignment. I'd never write things that way, by choice now, but back then it was really interesting, and lead to many sleepless nights, as I did the usual student thing and try and get things done before the due date arrives.
ReplyDeleteYou can think of a visitor as a glorified switch statement.
ReplyDeleteThe simplest version is probably to have just one callback and switch on the statement type:
http://golang.org/pkg/go/ast/#Walk
google 2403
ReplyDeletegoogle 2404
google 2405
google 2406
google 2407