Post on 18-Dec-2015
Aspect-Oriented Programming
Gregor KiczalesUniversity of British Columbia
© Copyright 2004, Gregor Kiczales. All rights reserved.
CASCON 2004
2
Contents
• What is AOP• How AOP works• What AOP does for code and designs• What’s happening with AOP today
CASCON 2004
3
Consider developing…
a simple drawing application (JHotDraw)
CASCON 2004
4
Intuitively thinking of objects?
• Points, Lines…• Drawing surfaces• GUI Widgets• …
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
moveBy(int, int)
*
CASCON 2004
5
most programmers would have used
this has poor design and code modularity!
22 12
65 93
43 29
86 65
2 4
collection of procedures to operate on and
manage table entries
6 7 5 8
+
In 1969…
CASCON 2004
6
What is OOP?
• a learned intuitive way of thinking• design concepts
– objects, classification hierarchies
• supporting mechanisms– classes, encapsulation, polymorphism…
• allows us to– make code look like the design– improves design and code modularity
many other benefits build
on these
CASCON 2004
7
class Point extends Shape {
private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; display.update(this); } void setY(int y) { this.y = y; display.update(this); }}
fair design modularitybut poor code modularity
1
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
moveBy(int, int)
*
But some concerns “don’t fit”
i.e. a simple Observer pattern
CASCON 2004
8
aspect ObserverPattern { private Display Shape.display;
pointcut change(): call(void figures.Point.setX(int)) || call(void Point.setY(int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Shape.moveBy(int, int)); after(Shape s) returning: change() && target(s) { s.display.update(); }}
ObserverPattern
1
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
moveBy(int, int)
*
With AOP they do fit
good design modularitygood code modularity
CASCON 2004
9
aspect ObserverPattern { private Display Shape.display;
pointcut change(): call(void Shape.moveBy(int, int)) || call(void Shape+.set*(..)); after(Shape s) returning: change() && target(s) { s.display.update(); }}
ObserverPattern
1
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
moveBy(int, int)
*
Ask yourself: could you name asingle class “ObserverPattern” ?
Code looks like the design
CASCON 2004
10
What is AOP?
• a learned intuitive way of thinking• design concepts
– aspects, crosscutting structure
• supporting mechanisms– join points, pointcuts, advice…
• allows us to– make code look like the design– improve design and code modularity
CASCON 2004
11
Today, We All Intuitively See
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
makePoint(..)makeLine(..)moveBy(int, int)
*
CASCON 2004
12
Today, We All Intuitively See
operations that change shapes
factory methods
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
makePoint(..)makeLine(..)moveBy(int, int)
*
CASCON 2004
13
AOP Developers Intuitively See…
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
makePoint(..)makeLine(..)moveBy(int, int)
*
FactoryEnforcement
ObserverPattern
BoundsChecking
CASCON 2004
14
Mini-outline – next 15 minutes
• Explain how this AOP code works– Using AspectJ, leading AOP language
• Pointcuts-and-advice– dynamic join points, pointcuts, advice
• Inter-type declarations• Multiple versions of ObserverPattern• IDE demonstration• Evaluating modularity
– comparing AOP and non-AOP code
CASCON 2004
15
Method Execution Join Points
:Point
• method execution join point
• the entry/exit to the execution
• not the source code
setX(int)
CASCON 2004
16
More Method Execution Join Points
:Line end1:Point
moveBy(int, int) moveBy(int, int)setX(int)
setY(int)
moveBy(int, int)setX(int)
setY(int)
end2:Point
CASCON 2004
17
Method Call Join Points
:Line end1:Point
moveBy(int, int) moveBy(int, int)setX(int)
setY(int)
moveBy(int, int)setX(int)
setY(int)
end2:Point
CASCON 2004
18
Dynamic Join Points
all dynamic join points on this slide are within the control flow of
this dynamic join point
:Line end1:Point
moveBy(int, int) moveBy(int, int)setX(int)
setY(int)
moveBy(int, int)setX(int)
setY(int)
end2:Point
well-defined points in flow of execution
CASCON 2004
19
execution(void Line.setP1(Point))
Pointcuts
a pointcut is a predicate on dynamic join points that:– can match or not match any given join point– says “what is true” when the pointcut matches– can optionally expose some of the values at that join point
a means of identifying dynamic join points
matches method execution join points with this signature
CASCON 2004
20
Pointcut Composition
whenever a Line executes a “void setP1(Point)” or “void setP2(Point)” method
or
a “void Line.setP2(Point)” execution
a “void Line.setP1(Point)” execution
execution(void Line.setP1(Point)) || execution(void Line.setP2(Point));
pointcuts compose like predicates, using &&, || and !
CASCON 2004
21
Primitive Pointcuts
- call, execution - get, set- handler- initialization, staticinitialization
- within, withincode
- this, target, args
- cflow, cflowbelow
CASCON 2004
22
User-Defined Pointcuts
user-defined (aka named) pointcuts– can be used in the same way as primitive pointcuts
defined with pointcut declaration
pointcut change(): execution(void Line.setP1(Point)) || execution(void Line.setP2(Point));
name parameters
more on parameters and how pointcut can expose values at join points in a few slides
CASCON 2004
23
pointcut change(): execution(void Line.setP1(Point)) || execution(void Line.setP2(Point));
after() returning: change() { <code here runs after each change> }
After Advicea means of affecting semantics at dynamic join points
:Line
setP1(int)
after advice runs on the
way back out
CASCON 2004
24
A Simple Aspect
an aspect defines a special class that can crosscut other classes
ObserverPattern v1
box means complete running code
aspect ObserverPattern {
pointcut change(): execution(void Line.setP1(Point)) || execution(void Line.setP2(Point));
after() returning: change() { Display.update(); }}
CASCON 2004
25
Pointcutscan cut across multiple classes
pointcut change(): execution(void Line.setP1(Point)) || execution(void Line.setP2(Point)) || execution(void Point.setX(int)) || execution(void Point.setY(int));
CASCON 2004
26
Pointcuts can use interface signatures
pointcut change(): execution(void Shape.moveBy(int, int)) || execution(void Line.setP1(Point)) || execution(void Line.setP2(Point)) || execution(void Point.setX(int)) || execution(void Point.setY(int));
CASCON 2004
27
A Multi-Class AspectObserverPattern v2
aspect ObserverPattern {
pointcut change(): execution(void Shape.moveBy(int, int)) || execution(void Line.setP1(Point)) || execution(void Line.setP2(Point)) || execution(void Point.setX(int)) || execution(void Point.setY(int));
after() returning: change() { Display.update(); }}
CASCON 2004
28
Using a Naming ConventionObserverPattern v2b
aspect ObserverPattern {
pointcut change(): execution(void Shape.moveBy(int, int)) || execution(void Shape+.set*(*));
after() returning: change() { Display.update(); }}
CASCON 2004
29
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int)) || execution(void Shape+.set*(*));
after(Shape s) returning: change(s) { <s is bound to the figure element> }
Values at Join Points
• pointcut can explicitly expose certain values• advice can use explicitly exposed values
demonstration, not detailed explanation
parameter mechanism being used
CASCON 2004
30
Revised AspectObserverPattern v3
aspect ObserverPattern {
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int)) || execution(void Shape+.set*(*)));
after(Shape s): change(s) { Display.update(s); }}
CASCON 2004
31
aspect ObserverPattern {
private Display Shape.display;
static void setDisplay(Shape s, Display d) { s.display = d; }
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int)) || execution(void Shape+.set*(*)));
after(Shape shape): change(shape) { shape.display.update(s); }}
One Display per ShapeObserverPattern v4
• inter-type declarations• declares members of other types
– fields, methods
CASCON 2004
35
• Aspect cuts new interface– through Point and Line
• DisplayUpdating itself is modular
aspect ObserverPattern {
private Display Shape.display;
public void Shape.setDisplay(Display d) { this.display = d; }
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int) || execution(void Shape+.set*(*)));
after(Shape s) returning: change(s) { Display.update(s); }}
Crosscutting Structure and Interfaces
class Line { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point {
private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
36
IDE support
• AJDT (AspectJ Development Tool)• An Eclipse Project• Goal is JDT-quality AspectJ support
– highlighting, completion, wizards…– structure browser
• immediate• outline• overview
CASCON 2004
37
Modularity Assessment
• Use a simple evolution scenario
CASCON 2004
38
Without AspectJ
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1;
} void setP2(Point p2) { this.p2 = p2;
}}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x;
} void setY(int y) { this.y = y;
}}
CASCON 2004
39
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; Display.update(); } void setP2(Point p2) { this.p2 = p2; Display.update(); }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x;
} void setY(int y) { this.y = y;
}}
Without AspectJObserverPattern v1
CASCON 2004
40
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; Display.update(); } void setP2(Point p2) { this.p2 = p2; Display.update(); }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; Display.update(); } void setY(int y) { this.y = y; Display.update(); }}
Without AspectJObserverPattern v2
CASCON 2004
41
Without AspectJObserverPattern v3
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; Display.update(this); } void setP2(Point p2) { this.p2 = p2; Display.update(this); }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; Display.update(this); } void setY(int y) { this.y = y; Display.update(this); }}
CASCON 2004
42
class Shape { private Display display;
abstract void moveBy(int, int);}
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; Display.update(this); } void setP2(Point p2) { this.p2 = p2; Display.update(this); }}
class Point extends Shape { ...}
Without AspectJ
“display updating” is not modular– evolution is cumbersome– changes are scattered– have to track & change all callers– it is harder to think about
ObserverPattern v4
CASCON 2004
43
With AspectJ
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
44
With AspectJ
aspect ObserverPattern {
pointcut change(): execution(void Line.setP1(Point)) || execution(void Line.setP2(Point));
after() returning: change() { Display.update(); }}
ObserverPattern v1
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
45
With AspectJObserverPattern v2
aspect ObserverPattern {
pointcut change(): execution(void Shape.moveBy(int, int) || execution(void Line.setP1(Point)) || execution(void Line.setP2(Point)) || execution(void Point.setX(int)) || execution(void Point.setY(int));
after() returning: change() { Display.update(); }}
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
46
With AspectJObserverPattern v2.5
aspect ObserverPattern {
pointcut change(): execution(void Shape.moveBy(int, int) || execution(void Shape+.set*(*));
after() returning: change() { Display.update(); }}
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
47
aspect ObserverPattern {
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int) || execution(void Shape+.set*(*)));
after(Shape s) returning: change(s) { Display.update(s); }}
With AspectJObserverPattern v3
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
CASCON 2004
48
ObserverPattern is modular– all changes in single aspect– evolution is modular– it is easier to think about
With AspectJObserverPattern v4
class Line extends Shape { private Point p1, p2;
Point getP1() { return p1; } Point getP2() { return p2; }
void setP1(Point p1) { this.p1 = p1; } void setP2(Point p2) { this.p2 = p2; }}
class Point extends Shape { private int x = 0, y = 0;
int getX() { return x; } int getY() { return y; }
void setX(int x) { this.x = x; } void setY(int y) { this.y = y; }}
aspect ObserverPattern {
private Display Shape.display;
static void setDisplay(Shape s, Display d) { s.display = d; }
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int)) || execution(void Shape+.set*(*)));
after(Shape shape): change(shape) { shape.display.update(s); }}
CASCON 2004
49
Modularity Assessment
• The aspect is– localized– has a clear interface
• The classes are– better localized (no invasion of updating)
• Code modularity helps design modularity– simple observer pattern is simple
• The code looks like the design• Forest vs. trees
– the crosscutting structure is explicit, clear, modular– the local effects can be made clear by IDE
CASCON 2004
50
Review So Far
• Aspect is a software design concept– supported by mechanisms– a “learned intuitive way of thinking”
• Mechanisms– Pointcuts and advice
• dynamic join points, pointcuts, advice– Inter-type declarations
• Different concepts for different structure of concerns– procedure holds computeRadius, setX…– class holds Point, Line…– aspect holds ObserverPattern…
• Aspects– modular units of implementation– look like modular units of design– improves design and code
CASCON 2004
51
Dynamic Join Points
• several kinds of dynamic join points– method & constructor execution– method & constructor call– field get & set– exception handler execution– static & dynamic initialization
method callmethod
execution
:Point
setX(int)
well-defined points in flow of execution
CASCON 2004
52
Only Top-Level ChangesObserverPattern v5
aspect ObserverPattern {
pointcut change(Shape shape): this(shape) && (execution(void Shape.moveBy(int, int)) || execution(void Line.setP1(Point)) || execution(void Line.setP2(Point)) || execution(void Point.setX(int)) || execution(void Point.setY(int)));
pointcut topLevelchange(Shape s): change(s) && !cflowbelow(change(Shape));
after(Shape shape) returning: topLevelchange(shape) { Display.update(shape); }}
CASCON 2004
53
Pre-conditionsusing before advice
aspect BoundsPreConditionChecking { before(int newX): execution(void Point.setX(int)) && args(newX) { check(newX >= MIN_X); check(newX <= MAX_X); } before(int newY): execution(void Point.setY(int)) && args(newY) { check(newY >= MIN_Y); check(newY <= MAX_Y); }
private void check(boolean v) { if ( !v ) throw new RuntimeException(); }}
what follows the ‘:’ is always a pointcut – primitive or user-defined
CASCON 2004
54
Design Invariants
aspect FactoryEnforcement {
pointcut newShape(): call(Shape+.new(..));
pointcut inFactory(): within(Point Shape.make*(..));
pointcut illegalNewShape(): newShape() && !inFactory();
before(): illegalNewShape() { throw new RuntimeError("Must call factory method…“); }}
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
makePoint(..)makeLine(..)moveBy(int, int)
*
CASCON 2004
55
Design Invariants
aspect FactoryEnforcement {
pointcut newShape(): call(Shape+.new(..));
pointcut inFactory(): within(Point Shape.make*(..));
pointcut illegalNewShape(): newShape() && !inFactory();
declare error: illegalNewShape(): "Must call factory method to create figure elements.";
}
Display
2Point
getX()getY()setX(int)setY(int)moveBy(int, int)
Line
getP1()getP2()setP1(Point)setP2(Point)moveBy(int, int)
Shape
makePoint(..)makeLine(..)moveBy(int, int)
*
CASCON 2004
56
Property-Based Pointcuts
Consider code maintenance• Another programmer adds a public method
• i.e. extends public interface – this code will still work• Another programmer reads this code
• “what’s really going on” is explicit
aspect PublicErrorLogging {
Log log = new Log();
pointcut publicInterface(): call(public * com.acme..*.*(..));
after() throwing (Error e): publicInterface() { log.write(e); }}
neatly captures public interface of mypackage
CASCON 2004
57
Swing Thread Safety[Laddad ’03]
public abstract aspect SwingThreadSafetyAspect { abstract pointcut uiMethodCall();
pointcut threadSafeCall():call(void JComponent.revalidate())|| call(void JComponent.repaint(..))|| call(void add*Listener(EventListener))|| call(void remove*Listener(EventListener));
pointcut excludedJoinPoint():threadSafeCall()|| within(SwingThreadSafetyAspect+)|| if(EventQueue.isDispatchThread());
Object around() : uiMethodCall() && !excludedJoinPoint() { /* run in another thread… */ }}
CASCON 2004
58
Reusable Patterns
abstract aspect ObserverPattern { protected interface Subject { } protected interface Observer { } public void addObserver(Subject s, Observer o) { ... } public void removeObserver(Subject s, Observer o) { ... } public List getObservers(Subject s) { ... }
abstract pointcut change(Subject s);
after(Subject s): change(s) { Iterator iter = getObservers(s).iterator(); while ( iter.hasNext() ) { notifyObserver(s, ((Observer)iter.next())); } } abstract void notifyObserver(Subject s, Observer o);}
[Hanneman OOPSLA’02]
CASCON 2004
59
Concrete Reuse
aspect DisplayUpdating extends ObserverPattern {
declare parents: FigureElement implements Subject; declare parents: Display implements Observer;
pointcut change(Subject s): target(s) && (call(void FigureElement.moveBy(int, int)) || call(void Line.setP1(Point)) || call(void Line.setP2(Point)) || call(void Point.setX(int)) || call(void Point.setY(int)));
void notifyObserver(Subject s, Observer o) { ((Display)o).update(s); }}
[Hanneman OOPSLA’02]
CASCON 2004
61
Other Aspects
• Security– pointcut for when checking happens– makes invariant clear, enforced
• Optimization• Distribution• Synchronization• Persistence• and of course, Logging
– IBM story• and very many application-specific aspects
– i.e. EnsureLiveness
CASCON 2004
62
Benefits of AOP
• All the usual modularity benefits– clarity– reusability– quality– easier to develop– configuration management– product line management– IP management– testing– …
CASCON 2004
63
Use Cases
• Each use case is realized by a collaboration - a set of classes• A class plays different roles in different use case realizations• The total responsibility of a class is the composition of these
roles
Withdraw Cash
Deposit Funds
Transfer Funds
Cash Withdrawal
Cash Interface
Cash Interface Deposit Funds
Transfer Funds
Interface Interface
Funds Deposit
Cash Transfer Funds
CashWithdrawal
Use case Specification
Use case design
Component design &implementation
CASCON 2004
64
More Existing Crosscutting
• join points– method call reception return
aStateChange()
_aStateChange()
notify()update()
Subject Observer
CASCON 2004
65
Industry Adoption
• AOP adoption is happening very fast• Enterprise Java is most active domain
– AspectWerkz, JBoss (JavaAssist), Spring, …– IBM actively using AspectJ in Websphere …– BEA, Oracle actively evaluating– Hot topic at TSS Symposium and JavaOne
• And this year…– Danny Sabbah (IBM VP) commits to AOP
in 3 of 5 main product lines– Gates says Microsoft will adopt AOP
CASCON 2004
66
Summary
• AOP is a learned intuitive way of thinking– aspects, crosscutting structure
• Supporting mechanisms– join points, pointcuts, advice, inter-type declarations
• Allows us to– make code look like the design– improve design and code modularity
• A practical way to improve your– software designs, code, product, productivity
CASCON 2004
67
• Composition Patterns [Clarke, Walker]
AOP support in UML
Subject
+ addObserver(Object)+ removeObserver(Object)+ aStateChange(..)# _aStateChange(..)- notify()
subjects *
Vector
observers1
Observer
+ update()+ start(..,Subject,..)# _start(..,Subject,..)+ stop(..,Subject,..)# _stop(..,Subject,..)
«subject»Observer <Subject, _aStateChange(..)>
<Observer, update(), _start(..,Subject,..), _stop(..,Subject,..)>
CASCON 2004
68
Aspect Enabled Refactoring Mockup
CASCON 2004
69
Fluid Aspects
CASCON 2004
70
Fluid Aspects
CASCON 2004
71
All Kinds of Crosscutting
aStateChange()
_aStateChange()
notify()update()
Subject Observer
CASCON 2004
77
Observer
Subject
Observer: Display
change
update: {...}
class Point {
int x; Getter/Setter
int y;
void draw() {
Graphics.drawOval(…);
}
...
}
Superposition of Patterns and Code
• pattern must be effective (connect to code)• ‘wizards’ weave the pattern in• but what happens if code changes?
CASCON 2004
78
Observer
Subject
Observer: Display
change
update: {...}
class Point {
int x; Getter/Setter
int y;
void draw() {
Graphics.drawOval(…);
}
...
}
Making ‘Generators’ Work Together
• Observer can refer to expansion of Getter/Setter– clearly, reliably, stably– alternate reference, beyond causal reach, indexical
• multiple routes to reference• beyond causal reach• indexical