© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Observer Pattern
Vejledende løsninger

 

 

1 PrintObserver er så simpel som nogen Observer kan være
class PrintObserver implements Observer {
  
  public PrintObserver( Subject sub ) {
    sub.addObserver( this );
  }

  public void update( Subject sub ) {
    System.out.println( "State changed to: " + ((ConSubject) sub).getState() );
  }
}
Dernæst er der AlarmObserver:
class AlarmObserver implements Observer {
  private int grænseVærdi;

  public AlarmObserver( Subject s, int grænseVærdi ) {
    this.grænseVærdi = grænseVærdi;

    s.addObserver( this );
  }

  public void update( Subject sub ) {
    if ( ((ConSubject) sub).getState() > grænseVærdi )
      System.out.println( "Alarm!" );
  }
}
Endelig har vi en testanvendelse der tilmelde en instans af hver af de to observere, og lader Subjects tilstands gå fra 1 to 4:
class TestObserver {
  
  public static void main( String argv[] ) {
    Observer obsA, obsB;
    ConSubject sub;
    
    sub = new ConSubject( 1 );
    
    obsA = new AlarmObserver( sub, 3 );
    obsB = new PrintObserver( sub );
    
    for ( int i=2; i<5; i++ )
      sub.changeState( i );
  }
}

State changed to: 2
State changed to: 3
Alarm!
State changed to: 4

Man bemærker at instansen af AlarmObserver, er den første som får besked om tilstandsændringer.

Kildetekster:

Observer.java
AlarmObserver.java

PrintObserver.java

Subject.java
ConSubject.java
TestObserver.java

 

2 Vores to interfaces Subject og Observer fjernes og i stedet bruges klassen Observable (erstatter Subject) og interfacet Observer, begge fra java.util.
Først er der AlarmObserver:
import java.util.*;

class AlarmObserver implements Observer {
  private int grænseVærdi;

  public AlarmObserver( ConSubject sub, int grænseVærdi ) {
    this.grænseVærdi = grænseVærdi;

    sub.addObserver( this );
  }

  public void update( Observable sub, Object obj ) {
    if ( ((ConSubject) sub).getState() > grænseVærdi )
      System.out.println( "Alarm!" );
  }
}
Vi gør her parameteren til konstruktoren konkret. I update-metoden caster vi Subject til ConSubject når vi skal foretage getState-kaldet. Ved at gøre konstruktorens parameteren konkret sikrer vi os, at vi kun kan tilmelde os et ConSubject. Derved flytter vi en potentiel run-time fejl til en compile-fejl (den fejl, der ligger i at parameteren til update-metoden evt. ikke er et ConSubject).
PrintObserver er som følger:
import java.util.*;

class PrintObserver implements Observer {
  
  public PrintObserver( ConSubject s ) {
    s.addObserver( this );
  }

  public void update( Observable sub, Object obj ) {
    System.out.println( "State changed to: " + ((ConSubject) sub).getState() );
  }
}
ConSubject-klassen får fra eksemplet i kapitlet.
Ved refactoring må man ikke ændre i klienten (idet interfacet ikke må ændres), men vi har undtagelsesvis gentaget den her, da der er én (lille) ændring - Kan du se hvilken?
import java.util.*;

class TestObserver {
  
  public static void main( String argv[] ) {
    Observer obsA, obsB;
    ConSubject sub;
    
    sub = new ConSubject( 1 );
    
    obsA = new AlarmObserver( sub, 3 );
    obsB = new PrintObserver( sub );
    
    for ( int i=2; i<5; i++ )
      sub.changeState( i );
  }
}

State changed to: 2
State changed to: 3
State changed to: 4
Alarm!

Samtidig er den en ændring i udskriften, idet det ses at Observable's container ordner Observer'ne i en anden rækkefølge end vores egen implementation med LinkedList (dette udelukker naturligvis ikke at Observable bruger LinkedList - jeg er ikke bekendt med hvilken container Observable rent faktisk bruger!)

Kildetekster:

AlarmObserver.java
PrintObserver.java

ConSubject.java
TestObserver.java

 

3 Først har vi Ship - vores Subject:
import java.util.*;

class Ship extends Observable {
  
  public static final int FLOATING = 0;
  public static final int SINKING  = 1;
  
  private int state;
  
  public Ship() {
    state = FLOATING;
  }
  
  public void sink() {
    state = SINKING;
    setChanged();
    notifyObservers();
  }
  
  public int getState() {
    return state;
  }
}
Vi bruger to klasse-konstanter til at repræsentere tilstanden.
Dernæst laver vi Person, som er vores Observer:
import java.util.*;

class Person implements Observer {
  
  private String name;
  private String reply;
  
  public Person( Ship skib, String n, String r ) {
    name = n;
    reply = r;
    skib.addObserver( this );
  }
  
  public void update( Observable thing, Object obj ) {
    if ( ((Ship) thing).getState() == Ship.SINKING ) {
      System.out.println( name + " says:" );
      System.out.println( "   \"" + reply + "\"" );
    }
  }
}
Person melder sig selv til som Observer.
Bemærk, at update-metoden ikke laver nogen kontrol af om Observable er et Ship, da den ved, at den kun melder sig som Observer hos et Ship.
Dernæst har vi en testanvendelse, med tre personer:
class TestObserver {
  
  public static void main( String argv[] ) {
    
    Ship titanic = new Ship();
    
    new Person( titanic, 
      "Some dude", 
        "Cool man - it's kind of going down into the water man" );
    new Person( titanic, 
      "Gill Bates", 
        "Sinking - gives me an idea for a new feature" );
    new Person( titanic, 
      "The Captain", 
        "Ladies and Gentlemen - everything is under control" );
    new Person( titanic, 
      "Bruce", 
        "Going down under, mate - Heading back home" );
    
    titanic.sink();
  }
}

Bruce says:
   "Going down under, mate - Heading back home"
The Captain says:
   "Ladies and Gentlemen - everything is under control"
Gill Bates says:
   "Sinking - gives me an idea for a new feature"
Some dude says:
   "Cool man - it's kind of going down into the water man"

Kildetekster:

Ship.java
Person.java

TestObserver.java

 

4 Abonnent'en tilmelder sig selv som Observer, efter at have fået en reference til Singletonen fra et instance-kald.
class Abonnent {
  
  private String navn;
  
  public Abonnent( String n ) {
    navn = n;
    HovedVindue.instance().tilføjAbonnent( this );
  }
  
  public void vindueÆndret( HovedVindue vindue ) {
    System.out.println( navn + ": " + vindue.navn() +
      " er ændret til " + vindue.toString() );
  }
}
Bemærk at parameteren til vindueÆndret er overflødig, da Singletonen er globalt tilgængelig med et instance-kald.
Dernæst følger HovedVindue, der er en sammenfletning af en Singleton og et Subject:
import java.util.*;

class HovedVindue {
  
  private static HovedVindue Instance=null;
  
  private int bredde, højde;
  
  private LinkedList abonnenter;
  
  private HovedVindue() {
    bredde = højde= 0;
    
    abonnenter = new LinkedList();
  }
  
  public static HovedVindue instance() {
    if ( Instance == null )
      Instance = new HovedVindue();
      
    return Instance;
  }
  
  public void ændreStørrelse( int nyBredde, int nyHøjde ) {
    bredde = nyBredde;
    højde = nyHøjde;
    
    ændret();
  }

  private void ændret() {
    for ( int i=0; i<abonnenter.size(); i++ )
      ((Abonnent) abonnenter.get( i ) ).vindueÆndret( this );
  }
  
  public void tilføjAbonnent( Abonnent a ) {
    abonnenter.add( a );
  }
  
  public String navn() {
    return "Hoved vindue";
  }
  
  public String toString() {
    return "[bredde=" + bredde + ", højde=" + højde + "]";
  }
}
Testanvendelsen laver tre Observere og foretager en ændring af vinduets størrelse:
class TestObserver {
  
  public static void main( String argv[] ) {
    Abonnent a1 = new Abonnent( "A" );
    Abonnent a2 = new Abonnent( "B" );
    Abonnent a3 = new Abonnent( "C" );
    
    HovedVindue.instance().ændreStørrelse( 250, 400 );
  }
}

A: Hoved vindue er ændret til [bredde=250, højde=400]
B: Hoved vindue er ændret til [bredde=250, højde=400]
C: Hoved vindue er ændret til [bredde=250, højde=400]

Kildetekster:

HovedVindue.java
Abonnent.java
TestObserver.java