Nada

Föreläsning 2: Inre klasser, paket, designmönster.

Lyssnare

Tangenttryckningar och musrörelser ger upphov till ett Event-objekt med information om vad som skett. I den första java 1.0 skickades alltid detta meddelande till metoden action, vare sej någon var intresserad av det eller ej. Det blev långsamt och det kunde bli mycket att ta hand om i samma metod. Senare javaversioner skickar eventobjektet till det lyssnarobjekt som installeras med en sats av typen
  knapp.addActionListener(lyssnare)
Här ska lyssnare vara ett objekt med en metod
public void actionPerformed(ActionEvent e)
som anropas vid varje knapptryckning. Närmare bestämt ska lyssnarklassen implementera gränssnittet ActionListener.

Det naturligaste kan tyckas vara att skriva en knapplyssnarklass

  class Lyssnare implements ActionListener    {
    public void actionPerformed(ActionEvent e){
      Button knappen = (Button) e.getSource() ;
      knappen.setText("Bra tryck!")           ;}}
och skapa lyssnarobjektet med Lyssnare lyssnare = new Lyssnare(); men det har nackdelen att den utomstående klassen Lyssnare inte kommer åt privata metoder och data i huvudklassen. Den vanligaste lösningen är att låta huvudklassen själv vara lyssnare.
  class Huvud extends Frame implements ActionListener{
    - - -
      Button knapp = new Button("Tryck")             ;
      knapp.addActionListener(this)                  ;
    - - -
    public void actionPerformed(ActionEvent e)       {
      knapp.setText("Bra tryck!")                    ;}}
Det här funkar bra för knappar och textfält eftersom ActionListener bara har en metod, men gränssnittet WindowListener har sju metoder, eftersom fönster kan öppnas, stängas, aktiveras, krympas till ikon osv. Även om bara en av metoder ska användas måste en WindowListener innehålla alla sju. För att förenkla programmerarlivet har java en färdig lyssnarklass WindowAdapter, som implementerar alla sju metoderna men inte gör någonting. Det finns adapterklasser till alla lyssnargränssnitt, faktiskt även till ActionListener. Vi kunde alltså lika gärna ha skrivit så här.
  class Lyssnare extends ActionAdapter        {
    public void actionPerformed(ActionEvent e){
      Button knappen = (Button) e.getSource() ;
      knappen.setText("Bra tryck!")           ;}}
En typisk fönsterlyssnare kan skrivas lika kort med en adapter.
  class FonsterLyssnare extends WindowAdapter {
    public void windowClosing(WindowEvent e)  {
      System.exit(0)                          ;}}
Adaptern kan inte användas för att göra Huvud-klassen till lyssnare, eftersom extend bara kan göras på en klass och Huvud-klassen redan ärver Frame.

Inre klass

Naturligare är det att lägga lyssnarklassen som en inre klass i huvudklassen, så här:
  class Huvud extends Frame                     {
    class Lyssnare implements ActionListener    {
      public void actionPerformed(ActionEvent e){
      knapp.setText("Bra tryck!")               ;}}
    - - -
    Button knapp = new Button("Tryck")          ;
    knapp.addActionListener(new Lyssnare())     ;
    - - -                                       }
I en inre klass finns alla metoder och variabler i den yttre klassen tillgängliga, men det blir olika objekt och man kan skapa flera objekt av den inre klassen som alla hör ihop med ett och samma yttre objekt. Ett inre objekt kallar sej själv för this och sitt yttre objekt för (i vårt exempel) Huvud.this.

Allra mest slimmad blir koden med en anonym inre klass.

  fonster.addWindowListener(new WindowAdapter(){
    public void windowClosing(WindowEvent e)   {
      System.exit(0)                           ;}});
Efter kompilering syns en inre klass som Huvud$Lyssnare.class och en anonym klass som Huvud$1.class.

Paket

När man överst i javaprogrammet skriver import java.awt.*; känner plötsligt kompilatorn till en massa biblioteksklasser, nämligen alla som ligger i paketet awt. Vill man själv samla några egna klasser i paketet egna gör man så här.
  1. Skriv package egna; allra överst i dessa javafiler och kompilera om dom.
  2. Skapa en underkatalog egna och lägg ner javafilerna och klassfilerna där.
  3. Nu fungerar import egna.* men bara i den katalog som har underkatalogen egna.
  4. I annan katalog måste javac och java få veta sökvägen till katalogen som har underkatalogen egna.
    java -classpath .:/home/teach/henrik/paket Huvud.java
    Om man glömmer .: först i sökvägen letar inte java på aktuell katalog och hittar alltså inte huvudprogrammet!
  5. Alternativt kan man i Unix göra en länk till paketkatalogen
    ln -s ~henrik/paket paket
    Skenbart blir då paket en vanlig underkatalog.

Designmönster

År 1995 gav fyra författare ut Design Patterns som namngav och beskrev tjugotre allmänt användbara klassdiagram. Många av dom förekommer i javas klassbibliotek och det är nyttigt att man har dom klart för sej när man designar egna klasser.

Observer

Det här är det designmönster som lyssnarklasserna följer.
     _______________            _______
    |Observable     |          /Observer\  Subjektet kan göra många
    |---------------|*        *|--------|  addObserver() och varje   
    |addObserver(..)|__________|update()|  lyssnare kan lyssna på
    |notify()       |          |        |  många subjekt. Metoden
    |_______________|          \________/  notify() anropar alla
           ^                       ^       lyssnares update().
           |                       |
           |                               Varje grafisk komponent är
         __|____                ___|____   Observable med metodnamn
        |Subjekt|              |Lyssnare|  som addActionListener  och
        |_______|<- - - - - - -|________|  actionPerformed i stället för 
                                           addObserver och update.
Den streckade pilen från lyssnaren till subjektet kommer sej av att anropet till
     public void actionPerformed(ActionEvent e) 
skickar med en referens till subjektet. Den kan man få fram med
     subjekt=e.getSource(); 
Fördelen är den lösa kopplingen mellan subjekt och lyssnare. Det enda subjektet vet om lyssnaren är att den implementerar gränssnittet Observer (runda hörn är svårt att rita!) och lyssnaren behöver inte veta någonting om subjektet. Klassen Observable har en lista över anslutna lyssnare och ser till att dom anropas när subjektet säger notify(). Som parametrar till update kan man skicka med objekt som talar om vad som hänt och var det hänt.

Model-View-Control

Verklighetens objekt kan ofta i programmet med fördel delas upp i tre olika objekt. Ta en klocka som exempel. Den beskrivs logiskt av vissa variabler (visat datum, visad tid, om den går eller ej, hur många timmar till batteriet räcker osv) och det samlar vi i ett objekt som kallas model. Sen har klockan en viss grafisk utformning och det blir ett objekt view. Slutligen kan klockan ställas om, bytas batteri på osv och det blir objektet control. Alla swingkomponenter är uppbyggda efter denna princip, fast control och view slagits samman till ett objekt. När man på skärmen ser en JButton finns alltså även ett objekt som implementerar gränssnittet ButtonModel. Normalt är det ett objekt av klassen DefaultButtonModel men det kan man ändra på om man vill.
    _______        ______       _________
   | Model |------| View |-----| Control |
   |_______|      |______|     |_________|  
I det här UML-diagrammet är det oklart om modellen har en vy eller vyn har en modell eller om båda har varandra. I verkligheten förekommer alla varianterna.

Singleton

Designmönstret Singleton används när man vill förhindra att det skapas mer än ett objekt av en viss klass, och tricket är att göra konstruktorn private.
public class Singleton                 {
  private static Singleton unik = null ;
  private Singleton()                  {
     - - -                             }
  public static Singleton get()        {
    if(unik==null) unik=new Singleton(); 
    return unik                        ;}}
Exempel på användning av singleton kan vara fabriker, som man inte behöver flera exemplar av, krypteringsobjekt och referensobjekt till operativsystemet. UML-diagrammet får bara en ruta.
    ______________ 
   | Singleton    |
   |--------------|   
   |- unik        |    - anger private
   |--------------|
   |+ get()       |    + anger public, understrykning static
   |- Singleton() |    
   |______________|

Sidansvarig: <erikf@nada.kth.se>
Senast ändrad 19 januari 2005
Tekniskt stöd: <webmaster@nada.kth.se>