Die Unterscheidung der Ereignisse aufgrund des Typs (getClass())
den übergebenen Parameter ist veraltet. Mit Hilfe einer eigenen generischen
Observer-Implementierung kann die Aufgabe eleganter gelöst werden. Diese erhält
als Typparameter ein
enum mit der Liste aller auftretenden Ereignisse:
public
interface
Observer<Event
extends
Enum<Event>> {
void update(Observable<Event> o, Event e);
public
abstract
class
Observable<Event
extends
Enum<Event>> {
private List<Observer<Event>>
observerList
= new
ArrayList<Observer<Event>>();
void addObserver(Observer<Event> o) {
observerList.add(o); }
void notifyObservers() {
for
(Observer<Event> o :
observerList)
o.update(this,
Event event); }
…
Außer diesen
Observable-Methoden, die wir für die Uhr brauchen werden,
können die weiteren aus
java.util.Observable ähnlich implementiert werden.
Um den generischen Observer für die Uhr zu benutzen,
brauchen wir ein
enum aller auftretenden Ereignisse:
enum Ereignis {
EIN, AUS,
UM,
TICK, INIT }
Die Schnittstelle IModel erweitert jetzt
Observer<Ereignis>:
public
interface IModel extends
Observer<Ereignis> {
static
class Uhrzeit { … } // wie oben
Uhrzeit getUhrzeit();
long getFrequenz(); }
Bei der Ereignisbehandlung in der
Model-Implementierung, in der von
Observer<>
geforderte update()
entfällt die gefährliche Konvertierung beim Ermitteln des Ereignisses, weil
Observable
jetzt generisch ist:
public
void update(Observable<Ereignis> o, Ereignis e) {
switch (e) {
case EIN:
uhrAn = true;
break;
case AUS:
uhrAn = false;
break;
case UM:
frequenz =
frequenz == FREQUENZ ?
FREQUENZ_UM :
FREQUENZ;
break;
case
INIT:
case
TICK:
tick(); } }
Control
ist jetzt eine Unterklasse von
Observable<>:
public
class
Control
extends
Observable<Ereignis> {
public Control(final
IModel model) {
new
Thread() … } // wie oben, mit notifyObservers(Ereignis.TICK);
public
void
start() {
notifyObservers(Ereignis.INIT);
} }
In diesem Konstruktor findet nur die Initialisierung des
Ticken-Threads statt. In der getrennten
start()-Methode (die im
main nach dem
Hinzufügen der Observer
wie Model und
die
Views
aufgerufen wird) werden alle
Observer benachrichtigt (notifyObservers(Ereignis.INIT)),
dass sie ihre Initialisierung durchführen. Bei
Model ist dies nur ein
tick(); die
Views sollen ihren Oberflächenelementen Lauscher hinzufügen.
DigitalView verarbeitet diese Ereignisse in der
update()-Methode.
Die Knöpfe können wir jetzt in einem
Map den Ereignissen zuordnen und
für ihre Beschriftung die Ereignisnamen verwenden. Dies hat den Vorteil, dass
später neue Knöpfe (mit neuen Ereignissen) einfach hinzugefügt werden können:
public
class
DigitalView
extends
JFrame
implements
Observer<Ereignis> {
… // Konstanten und
Attribute wie früher
private Map<Ereignis, JButton>
knöpfe =
new
EnumMap<Ereignis, JButton>(Ereignis.class);
public DigitalView(IModel model) {
… //
Initialisierung der Oberfläche wie früher
for (final
Ereignis ereignis : Ereignis.values())
if (ereignis.ordinal() >= Ereignis.EIN.ordinal()
&&
ereignis.ordinal() <= Ereignis.UM.ordinal())
{
// nur für die Knopf-Steuerung-Ereignisse
(zwischen EIN und UM
JButton knopf =
new
JButton(ereignis.name());
knöpfe.put(ereignis,
knopf);
super.add(knopf);
}
knöpfe.get(Ereignis.EIN).setEnabled(false);
pack(); }
@Override
// Observer
public
void
update(final
Observable<Ereignis> o,
final
Ereignis e) {
switch
(e) {
case
EIN:
knöpfe.get(Ereignis.EIN).setEnabled(false);
knöpfe.get(Ereignis.AUS).setEnabled(true);
break;
case
AUS:
knöpfe.get(Ereignis.EIN).setEnabled(true);
knöpfe.get(Ereignis.AUS).setEnabled(false);
break;
case
TICK: Uhrzeit uhrzeit =
model.getUhrzeit();
anzeige.setText(String.format("%02d:%02d:%02d",
uhrzeit.getStunde(),
uhrzeit.getMinute(), uhrzeit.getSekunde()));
break;
case
INIT:
for
(final Ereignis ereignis : Ereignis.values()) {
JButton knopf = knöpfe.get(ereignis);
if (knopf !=
null) // nur
für die Knopf-Ereignisse
knopf.addActionListener(new
ActionListener() {
@Override
public
void
actionPerformed(ActionEvent e) {
o.notifyObservers(ereignis); } }); } } } }
In der letzten Programmzeile ist erkennbar, dass jede
Knopf-Ereignisbehandlungsmethode (actionPerformed) nur aus dem Aufruf von
notifyObservers()
besteht.
In
KreisView.update() müssen wir die Tastenschläge einzeln den
Ereignissen zuordnen:
@Override //
Observer
public
void update(final
Observable<Ereignis> o, Ereignis e) {
if (e != Ereignis.INIT)
// alle Oberflächenereignisse
repaint();
else
// INIT
super.addKeyListener(new
KeyAdapter() {
@Override
public
void
keyPressed(KeyEvent e) {
switch
(e.getKeyChar()) {
case
'E': o.notifyObservers(Ereignis.EIN);
break;
case
'A': o.notifyObservers(Ereignis.AUS);
break;
case
'U': o.notifyObservers(Ereignis.UM);
} } }); }
Auf diese Weise kann nun das Entwurfsmuster Observer für
die Nachrichtenverteilung innerhalb von MVC eingesetzt werden.
Version: 19. April 2012
© Prof. Solymosi, 20102 Beuth-Hochschule für Technik Berlin, Fachbereich VI (Informatik und Medien)
solymosibht-berlin.de