The Observer Pattern Using Aspect Oriented Programming
The Observer Pattern Using Aspect Oriented Programming
1 Introduction
Since the concept of design patterns were introduced in the article [4] written by
Gamma, Helm, Johnson, and Vlissides, the authors later to be known simply as
the gang of four, much work has been devoted to investigate design patterns. A
lot of the work has been used to identify other design patterns than the original
23 introduced in [3], books has been written about how to use and implement
patterns in dierent programming languages, and a lot has been written about
domain specic patterns. In spite of all the work done, the design patterns
are still considered reusable mostly at the conceptual level. As a consequence the
programmer still must implement the patterns for each application he is building.
In [2] it is noted that some architectural abstraction will sooner or later
become part of the language, suggesting that the best solution to the missing
reusability is to make the patterns part of the language. The drawback of this
approach is more complicated languages.
1
the solutions to the problems of the patterns listed in [3] are not considering
AOP technologies. The implementing languages are AspectS [7] for Smalltalk
and AspectJ [8] for Java. The reason for choosing AspectS is that aspects can be
applied at runtime and because the language has a very dierent instantiation
policy than AspectJ, which is choosen mainly because it is the most popular
language.
2 An example of AOP
To introduce some important concepts of AOP a very simple, but still usefull
and genuinly new, implementation of the Observer pattern is provided. Assume
2
introduction, which is located in line 13-15, introduces (read inserts) a method
update(Subject) in the Observer class. The pointcut in gure 1 is the part in
line 20 after the ':'. It denes some well dened place in the executing code, in
this case the assignment of a new value to state. The advice denotes what to do
when a pointcut is reached. Code can usually be executed before, after, and/or
around the pointcut. In the example the code is to be executed after the above
mentioned pointcut. The code to execute is within the brackets in line 21-24,
and simply calls update on all registered observers. The methods for adding and
removing observers are as in Java.
Although this example is very simple, it might be a very usefull implemen-
tation of the Observer pattern in case the code is unlikely to be changed. It is
very simple to understand, but still separates the observer from the subject. The
motivation for using a more complicated, but reusable, solution should be the
same as always.
In the original solution, the subject has at least two methods; addObserver(Observer)
and removeObserver(Observer). This choice of interface is not the only possible
one. Furthermore it must be decided where to place the methods of the inter-
face. Assuming that it is possible to make the pattern reusable, there are two or
three possibilities, depending on whether the pattern is realized as a class; at the
observer, subject, or pattern. The interface will be slightly aected by where the
methods are placed.
At the one extreme the original interface is kept. The resulting method names
can be seen in table 1. The consequence of using the interface in table 1 is that it
is only possible to introduce behavior at the class level (that is any observer class
must do the same in response to a change in a subject class). The behavior must
still be implemented (that is the observer must have some update logic, which
can be called by the subject).
3
Placed on the subject.
addObserver: anObserver Indicate that anObserver now listens for changes in the
subject.
removeObserver: anObserver Indicate that anObserver no more listens for changes in
the subject.
Placed on the observer.
observe: aSubject Indicate that aSubject now is observed by the observer.
stopObserving: aSubject Indicate that aSubject is no more observed by the ob-
server.
Placed on the pattern.
startObserving: anObserver subject: aSubject Explanation as for the observer case.
stopObserving: anObserver subject: aSubject Explanation as for the observer case.
At the other extreme it is possible to let any observer instance do some action
in response to a state change in any subject instance. The methods are listed
in table 2. Remark that an agent is used in the table. The role of the agent
is to do some actions in one or more observers whenever an event (that is a
state change) occurs in one or more subjects. Using this denition the agent
maps many observers to many subjects. Depending on the implementation it
is benecial to use a dierent mapping (one observer to many subjects or one
subject to many observers).
4
must be removed manually from every observer if some observer is to be used in
a context where it doesn't play that role.
Placing anAction in separate classes avoids the above problems, but the ap-
proach might lead to a breach of encapsulation, because the observer is forced to
expose the state that must be changed or methods to call when a subject change
occurs.
If the methods are placed on the pattern, the interface is a little more com-
plicated than in the two other cases, but besides from that there doesn't seem to
be any logical choice of placement.
4 Solution
A solution can be seen in gure 2. It has two classes ( Subject and Observer )
and an aspect ( Agent ) not counting subclasses. No specic interface for the
Observer pattern behavior is chosen, but it is decided to place the methods on
the Observer . The role of the Agent is to observe the Subject and execute
some code on behalf of the Observer each time a subject change occurs. Which
kind of subject change one can listen for is specied by subclassing the Agent .
The Observer is composed of zero or more Agent 's depending on which subject
5
5 A Smalltalk implementation
getPointcut
^ OrderedCollection
with: (AsJoinPointDescriptor targetClass: Subject targetSelector: #price:)
when a subject change occurs (in this case when the message price: is sent to a
Subject Subject
). The and Observer classes are unaected by the Observer
pattern only dening their own methods.
Agent
The class is doing the actual method calls when a subject event occurs.
It is listed in gure 4 and is a subclass of AsAspect meaning that it can introduce
adviceSubjectChange
^ AsBeforeAfterAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverInstanceSpecific. })
pointcut: [self getPointcut]
afterBlock: [:receiver :arguments :aspect :client :result |
doBlock copy fixTemps valueWithArguments: {receiver. self observer.}]
getPointcut
self subclassResponsibility
doBlock
^ doBlock
doBlock: aBlock
doBlock := aBlock
observer
^ observer
observer: newObserver
observer := newObserver
advice. In this case one advice is introduced, which activates a block of code in
6
an Observer each time a pointcut dened by its subclass (for instance the one
in gure 3) occurs. The ObserverPattern is not listed here, but it introduces
methods for managing all the agents in the Observer .
o := Observer new.
s := Subject new.
pattern := ObserverPattern new.
pattern addObserverClass: Observer.
pattern install.
a := o do: [:subject :observer | observer showMessage: 's name change to: ' , subject name]
when: SubjectNameChange
in: s.
s name: 'apple'.
o stopObserving.
s name: 'want be noticed'
pattern uninstall.
6 An AspectJ implementation
7
aspect ObserverPatternBinding extends ObserverPattern {
each time one wants to apply the pattern to a new class, remove the pattern from
some class, or change the implementation of the pattern. This is a drawback of
using AspectJ.
Since there are no blocks in AspectJ or Java, an Action class is introduced.
It has the method execute , which is called on subject changes. Due to some
lack of instantiation policy in AspectJ, the agent maps one subject instance to
many actions. This approach is very similar to the AOP implementation of the
original solution.
7 Related work
8
weak hashmap containing the observers per subject). Since there are introduction
mechanisms in AspectJ it seems to be a better solution to use this mechanism.
Other contributions to implementing design patterns using AOP are [6], provid-
ing an implementation of the Decorator and Visitor patterns, and [1] providing
an implementation of the Visitor pattern using AspectJ.
8 Conclusion
The primary goal was to obtain a reusable implementation of the Observer pat-
tern using AspectS/Smaltalk and AspectJ/Java respectively. This requirement
is met, since the only things left to do in the implementations is specifying the
bindings. That is which objects are observers and which subject changes there
are in the system. Furthermore the actions to do when a subject change occurs
must be provided, but that seems reasonable. The reusable part of the implemen-
tation is provided in the appendices A.1 and B.1. There are dierences between
the two implementations, which are mostly due to the dierences between the
implementing languages.
The AspectS implementation is dynamic because of the dynamic nature of the
language. At runtime it is both possible to replace the pattern implementation,
assign roles, and introduce new pointcuts. Since the pointcuts can only catch
message sends between objects and the throwing of exceptions, the granularity
of the subject events that can be caught are somewhat limited.
The AspectJ implementation is static, which are mostly due to the fact that
the advice are weaved into the codebase at compiletime. It is thus not possible,
without an extra eort, to change pattern implementation at runtime or assign
new roles. On the other hand the pointcuts that can be expressed are far more
detailed than in AspectS.
Both implementations raises the problem of breach of encapsulation. The ac-
tions that are dened can only access external methods and state on the observer.
9
As a consequence the observer might be forced to expose some state which were
meant to be internal. Furthermore the pointcuts in Smalltalk can only intercept
message sends and thrown exceptions. In order to be able to detect internal
subject changes it might be required to access this state through message sends,
which also leads to breach of encapsulation.
The conclusion is that AOP can make the Observer pattern reusable, provid-
ing an alternative to changing the language. A new consequence of using the new
Observer pattern solution is added to the list in [3, p. 296]; the reusability adds
complexity. For many applications the very simple solution provided in gure 1
might be as usefull in applications where the observer/subject relation is unlikely
to change.
Whether other patterns can be made reusable are the subject of future work,
some of which is already done in the articles listed in the references section. Still
a lot of work lies ahead before all of the patterns are fully explored.
References
[4] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design
patterns: Abstraction and reuse of object-oriented design. Lecture Notes in
Computer Science, 707:406431, 1993.
[5] J. Hannemann and G. Kiczales. Design pattern implementation in java and as-
Proceedings of the 17th Annual ACM conference on Object-Oriented
pectj. In
Programming, Systems, Languages, and Applications (OOPSLA), 2002.
[6] R. Hirschfeld, R. Lämmel, and M. Wagner. Design Patterns and Aspects
Modular Designs with Seamless Run-Time Integration. Proc. of the 3rd
In
German GI Workshop on Aspect-Oriented Software Development, Technical
Report, University of Essen, 2003.
[7] Robert Hirschfeld. Aspect-oriented programming with aspects.
10
[8] http://www.eclipse.org/aspectj/.
11
A The observer pattern implementation in Smalltalk.
Instance methods:
adviceSubjectChange
^ AsBeforeAfterAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverInstanceSpecific. })
pointcut: [self getPointcut]
afterBlock: [:receiver :arguments :aspect :client :result |
doBlock copy fixTemps valueWithArguments: {receiver. self observer.}]
getPointcut
self subclassResponsibility
doBlock
^ doBlock
doBlock: aBlock
doBlock := aBlock
observer
^ observer
observer: newObserver
observer := newObserver
Instance methods:
removeAllActivators
activatorsPerObserver valuesDo: [:bagOfActivators |
bagOfActivators do: [:activator | activator uninstall]]
init
activatorsPerObserver := Dictionary new.
observers := OrderedCollection new.
adviceAddTo
^ AsIntroductionAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverClassSpecific. })
pointcut: [self getPointcut: #add:to:]
introBlock: [:receiver :arguments :aspect :client |
arguments second addReceiver: arguments first]
adviceDoWhenIn
^ AsIntroductionAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverClassSpecific. })
pointcut: [self getPointcut: #do:when:in:]
introBlock: [:receiver :arguments :aspect :client |
| activator observerActivators |
activator := arguments second new.
12
activator observer: receiver.
activator doBlock: arguments first.
activator addReceiver: arguments third.
activator install.
observerActivators :=
activatorsPerObserver at: receiver ifAbsentPut: [Bag new].
observerActivators add: activator.
activator]
adviceRemoveFrom
^ AsIntroductionAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverClassSpecific. })
pointcut: [self getPointcut: #remove:from:]
introBlock: [:receiver :arguments :aspect :client |
arguments second removeReceiver: arguments first]
adviceStopObserving
^ AsIntroductionAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverClassSpecific. })
pointcut: [self getPointcut: #stopObserving]
introBlock: [:receiver :arguments :aspect :client |
(activatorsPerObserver at: receiver) do: [:a | a uninstall].
activatorsPerObserver removeKey: receiver.]
adviceUndo
^ AsIntroductionAdvice
qualifier: (AsAdviceQualifier attributes: { #receiverClassSpecific. })
pointcut: [self getPointcut: #undo:]
introBlock: [:receiver :arguments :aspect :client |
arguments first uninstall.
(activatorsPerObserver at: receiver) remove: arguments first]
getPointcut: theSelector
| o |
o := OrderedCollection new.
observers do: [:observerClass |
o add: (AsJoinPointDescriptor targetClass: observerClass targetSelector: theSelector)].
^ o
uninstall
self removeAllActivators.
super uninstall
addObserverClass: theClass
observers add: theClass
removeObserverClass: theClass
observers remove: theClass
Class methods:
new
^ super new init
13
Class methods:
runExample
| o s0 s1 a pattern|
o := Observer new.
s0 := Subject new.
s1 := Subject new.
pattern := ObserverPattern new.
pattern addObserverClass: Observer.
pattern install.
a := o do: [:subject :observer | observer showMessage: 's name change to: ' , subject name]
when: SubjectNameChange
in: s0.
o add: s1 to: a.
s0 name: 'apple'.
o stopObserving.
s1 name: 'pear'.
pattern uninstall.
s1 name: 'want be noticed'
createSubjectEventTable
SubjectEventConstants
at: #SubjectNameChange put: SubjectNameChangeAgent ;
at: #SubjectPriceChange put: SubjectPriceChangeAgent.
Instance methods:
showMessage: theMessage
Transcript show: '[observer] ' , theMessage ; cr.
Instance methods:
name
^ name
name: newName
name := newName
price
^ price
price: newPrice
price := newPrice
Instance methods:
getPointcut
^ OrderedCollection
14
with: (AsJoinPointDescriptor targetClass: Subject targetSelector: #name:)
Instance methods:
getPointcut
^ OrderedCollection
with: (AsJoinPointDescriptor targetClass: Subject targetSelector: #price:)
Action.java
Agent.java
import java.util.Iterator;
import java.util.Hashtable;
after() : action() {
Iterator i = activators.values().iterator();
while (i.hasNext()) {
((Action) i.next()).execute();
}
}
ObserverPattern.java
import java.util.Iterator;
import java.util.Hashtable;
15
}
//introductions in observer
private Hashtable Observer.installedActions = new Hashtable();
private int Observer.nextId = 0;
SubjectObserverAction.java
ConsoleLogger.java
16
ObserverMain.java
import it.edu.jborella.patterns.observer.SubjectObserverAction;
ObserverPatternBinding.java
import it.edu.jborella.patterns.observer.Agent;
import it.edu.jborella.patterns.observer.ObserverPattern;
SomeSubject.java
17
private int price;
private String name;
SomeSubjectNameChangeAgent.java
import it.edu.jborella.patterns.observer.Agent;
SomeSubjectPriceChangeAgent.java
import it.edu.jborella.patterns.observer.Agent;
18