Thoughts about subject/observer, publisher/subscriber, and self types in Java
- June 14, 2013
I am neither a Java aficionado nor a Java guru, but I use it as a vehicle for teaching programming at an undergraduate level.
In this post, I describe a simple situation where the need for a self type arises in Java. I present a way of simulating a self type in Java, and also suggest that in this case, by changing the code slightly, one can avoid the need for a self type in the first place. None of these ideas is new, but perhaps they deserve to be more widely known.
The Java library offers a simple implementation of the subject/observer design pattern. It takes the form of an Observable class, which maintains a list of observers, and an Observer interface, which states (in short) that an observer must be able to receive a message.
A subject is essentially a publisher
In the subject/observer design pattern, an observer is supposed to be
notified only when the state of the subject changes. Java’s
Observable
class provides a Boolean field called
changed
, together with getter and setter methods. The
method notifyObservers
does nothing unless
changed
is set, and clears it. This relatively simple logic
is independent of the point that interests me, so I will omit it from
this discussion.
As a result of this omission, the subject/observer pattern degenerates and becomes essentially a publisher/subscriber pattern, where a subject can decide at any time to send a message to all of its observers.
A key point of interest, though, is that the subject sends itself as the message (or as part of the message).
Java’s Observer and Observable are not generic
Have a look at Java’s Observer
interface. The update
method expects two arguments: the
subject that sends the message, and the message itself.
public interface Observer {
void update (Observable subject, Object message)
}
This is coarse, and slightly unsatisfactory. When someone implements
the Observer
interface, they will have in mind a specific
type of subjects (a subclass of Observable
) and a specific
type of messages. Thus, they will be forced to use an inelegant and
potentially unsafe downcast instruction.
A generic Observer
In order to avoid this, it seems obvious that one should create a
parameterized version of the Observer
interface.
public interface Observer<M> {
void notify (M message);
}
I have slightly over-simplified the interface by deciding that
notify
takes a single parameter: a message. In principle,
this is sufficient. If one wishes to convey the identity of the subject
to the observer, then one can send the subject itself as the message. If
one wishes to convey both the identity of the subject and some piece of
data, then the message can be a pair of these two values.
Of course, parameterizing the Observer
interface does
not solve the problem. It only moves the problem to the implementation
of the Subject
class.
A basic Subject
We can now implement a basic version of the Subject
class. In the definition of notifyObservers
, we decide that
the message sent to the observers will be this
, that is,
the subject itself. Thus, it seems that every observer must have type
Observer<BasicSubject>
.
public abstract class BasicSubject {
private final List<Observer<BasicSubject>> observers
= new LinkedList<Observer<BasicSubject>> ();
public void addObserver (Observer<BasicSubject> o)
{
.add(o);
observers}
public void notifyObservers ()
{
for (Observer<BasicSubject> o : observers)
.notify(this);
o}
}
This works, but is again not satisfactory. Someone who implements the
interface Observer<BasicSubject>
will again be forced
to cast from the type BasicSubject
down to some specific
subclass.
What am I? or, the need for a self type
A Scala programmer would know how to solve this problem. We need a
self type. That is, we would like the observers to have type
Observer<Self>
, where Self
is the type
of this
. In other words, Self
is an
as-yet-undetermined subtype of Subject
.
In Scala, one can introduce Self
as a type parameter and
constrain it to stand for the type of this
, via a
constraint of the form this : Self => ...
.
In OCaml, the same thing is possible. (Thanks to Gabriel Scherer for pointing this out.) The subject/observer pattern can be implemented as follows:
class type ['m] observer = object
method notify : 'm -> unit
end
class subject = object (self : 'self)
val mutable observers : 'self observer list = []
method add_observer o =
observers <- o :: observersmethod notify_observers () =
List.iter (fun o -> o#notify self) observers
end
Scala and OCaml are cool, but I teach Java, so let’s go back to it.
Simulating a Self type
As of version 7, Java does not have this feature, but we can simulate
it by declaring an abstract method, named self
, whose
return type is Self
, and which we intend to implement (in a
concrete subclass) by return this
.
The code is now:
public abstract class Subject<Self> {
private final List<Observer<Self>> observers
= new LinkedList<Observer<Self>> ();
public void addObserver (Observer<Self> o)
{
.add(o);
observers}
public void notifyObservers ()
{
for (Observer<Self> o : observers)
.notify(self());
o}
public abstract Self self ();
}
We could add the constraint that
Self extends Subject<Self>
, but it is not required
here.
When we later implement a concrete subclass of Subject
,
say Temperature
, we implement the method self
,
as follows.
public class Temperature extends Subject<Temperature> {
@Override public Temperature self ()
{
return this;
}
}
This may seem a bit heavy, and it is indeed so, but at least we have
been able to simulate a self type. One can now implement the interface
Observer<Temperature>
without a downcast.
Publishers are simpler than subjects
The need for self types arises because a subject sends itself as a message to an observer. If we did not make this decision at the level of the super-class, the code would be simpler, and we would still be able to make this decision at the level of the subclass.
Let’s see.
A subject becomes just a publisher, and the type parameter
Self
becomes M
, the type of the message that
is sent. The type M
is entirely undetermined at this
point.
public abstract class Publisher<M> {
private final List<Observer<M>> observers
= new LinkedList<Observer<M>> ();
public void addObserver (Observer<M> o)
{
.add(o);
observers}
public void notifyObservers (M message)
{
for (Observer<M> o : observers)
.notify(message);
o}
}
When we later implement a concrete subclass of
Publisher
, say Pressure
, we instantiate
M
with Pressure
itself. Then, we implement a
new version of notifyObservers
, which does not take a
parameter, by invoking the inherited notifyObservers
with
this
as a parameter.
public class Pressure extends Publisher<Pressure> {
public void notifyObservers ()
{
notifyObservers(this);
}
}
The end result is the same as in the Subject/Temperature
example. However, because we no longer need a self
method,
this version of the code is perhaps easier to explain to a non-expert
programmer.