11 Interfaces - grænseflader til objekter

Indhold:



Forudsættes af kapitel 12, Hændelser, kapitel 16, Flertrådet programmering, kapitel 17, Serialisering, kapitel 18, RMI og kapitel 21, Indre klasser.

Forudsætter kapitel 5, Nedarvning (og 9, Appletter og grafik for at forstå et eksempel).



I generel sprogbrug er et interface (da.: grænseflade, snitflade) en form for grænseflade, som man gør noget gennem. F.eks. er en grafisk brugergrænseflade (eng.: Graphical User Interface - GUI) de vinduer med knapper, indtastningsfelter og kontroller, som brugeren har til interaktion med programmet.


Vi minder om, at en klasse er definitionen af en type objekter. Her kunne man opdele i

  1. Grænsefladen - hvordan objekterne kan bruges udefra.
    Dette udgøres af navnene1 på metoderne, der kan ses udefra.

  2. Implementationen - hvordan objekterne virker indeni.
    Dette udgøres af variabler og programkoden i metodekroppene.


Et 'interface' svarer til punkt 1): En definition af, hvordan objekter bruges udefra. Man kan sige, at et interface er en "halv" klasse.


Et interface er en samling navne på metoder (uden krop)

Et interface kan implementeres af en klasse - det vil sige, at klassen definerer alle interfacets metoder sammen med programkoden, der beskriver, hvad der skal ske, når metoderne kaldes.


11.1 Definere et interface

Lad os definere et interface kaldet Tegnbar, der beskriver nogle metoder på objekter, der kan tegnes.


import java.awt.*;

public interface Tegnbar
{
  public void sætPosition(int x, int y);

  public void tegn(Graphics g);
}


I stedet for "class" erklæres et interface med "interface".

Metoder i et interface har ingen krop, alle metodeerklæringerne følges af et semikolon. Der kan ikke oprettes objekter ud fra et interface. Det kan opfattes som en "tom skal", der skal "fyldes ud" af en rigtig klasse, der implementerer metoderne (dvs. definerer kroppene).


Man ser, at tegnbare objekter:



I UML-notation (tegningen til højre) er Tegnbar-interfacet tegnet med kursiv. Alle metoderne er abstrakte (= ikke implementerede) og er derfor også tegnet kursivt.

11.2 Implementere et interface

Lad os nu definere en klasse, der implementerer Tegnbar-interfacet.


En klasse kan erklære, at den implementerer et interface, og så skal den definere alle metoderne i interfacet og give dem en metodekrop

Vi skal altså definere alle interfacets metoder sammen med programkoden, der beskriver hvad der skal ske, når metoderne kaldes.


import java.awt.*;

public class Stjerne implements Tegnbar
{
  private int posX, posY;

  public void sætPosition(int x, int y)  // kræves af Tegnbar
  {
    posX = x;
    posY = y;
  }

  public void tegn(Graphics g)          // kræves af Tegnbar
  {
    g.drawString("*",posX,posY);
  }
}


Her har klassen Stjerne "udfyldt skallen" for Tegnbar ved at skrive "implements Tegnbar" og definere sætPosition()- og tegn()-metoderne (vi har også variabler til at huske x og y).

11.2.1 Variabler af type Tegnbar

Man kan erklære variabler af en interface-type. Disse kan referere til alle slags objekter, der implementerer interfacet2. Herunder erklærer vi en variabel af type Tegnbar og sætter den til at referere til et Stjerne-objekt.


    Tegnbar t;
    t = new Stjerne();       // Lovligt, Stjerne implementerer Tegnbar


Stjerne-objekter er også af type Tegnbar. Ligesom ved nedarvning siger man, at der er relationen Stjerne er-en Tegnbar, og at t er polymorf, da den kan referere til alle slags Tegnbare objekter.


Man kan ikke oprette objekter ud fra et interface (der bare er en "skal" og intet siger om, hvordan metoderne er implementerede så hvordan skulle objektet reagere, hvis metoderne blev kaldt?).


    t = new Tegnbar();       // FEJL! Tegnbar er ikke en klasse

11.3 Eksempler med interfacet Tegnbar

Lad os udvide (arve fra) Terning til at implementere Tegnbar-interfacet. For at gøre koden kort har metoden tegn() en hjælpemetode ci(), der tegner en cirkel for et øje.


import java.awt.*;
public class GrafiskTerning extends Terning implements Tegnbar
{
  int x, y;

  public void sætPosition(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

  private void ci(Graphics g, int i, int j)
  {
    g.fillOval(x+1+10*i,y+1+10*j,8,8);             // Tegn fyldt cirkel
  }

  public void tegn(Graphics g)
  {
    int ø = værdi;
    g.drawRect(x,y,30,30);                         // Tegn kant

    if (ø==1) ci(g,1,1);                           // Tegn 1-6 øjne
    else if (ø==2) { ci(g,0,0); ci(g,2,2); }
    else if (ø==3) { ci(g,0,0); ci(g,1,1); ci(g,2,2); }
    else if (ø==4) { ci(g,0,0); ci(g,0,2); ci(g,2,0); ci(g,2,2); }
    else if (ø==5) { ci(g,0,0); ci(g,0,2); ci(g,1,1); ci(g,2,0); ci(g,2,2); }
    else { ci(g,0,0); ci(g,0,1); ci(g,0,2); ci(g,2,0); ci(g,2,1); ci(g,2,2); }
  }
}


Bemærk:


Lad os gøre det samme med et raflebæger. For variationens skyld lader vi bægeret altid have den samme position, ved at lade sætPosition()'s krop være tom.


import java.awt.*;
public class GrafiskRaflebaeger extends Raflebaeger implements Tegnbar
{
  public GrafiskRaflebaeger()
  {
    super(0);
  }

  public void sætPosition(int x, int y) {  } // tom metodekrop

  public void tegn(Graphics g)
  {
    g.drawOval(80,20,90,54);
    g.drawLine(150,115,170,50);
    g.drawLine(100,115,80,50);
    g.drawArc(100,100,50,30,180,180);
  }
}


Kunne vi have udeladt sætPosition()-metoden, der alligevel ikke gør noget? Nej, vi har lovet at implementere begge metoder, om det så blot er med en tom krop, idet vi skrev "implements Tegnbar".


En hvilken som helst klasse kan gøres til at være Tegnbar. Her er et tegnbart rektangel:


import java.awt.*;
public class Rektangel extends Rectangle implements Tegnbar
{
  public Rektangel(int x1, int y1, int width1, int height1)
  {
    super(y1,x1,width1,height1);
  }

  public void sætPosition(int x1, int y1)
  {
    x = x1;
    y = y1;
  }

  public void tegn(Graphics g)
  {
    g.drawRect(x,y,width,height);
  }
}

11.3.1 En applet af Tegnbare objekter

Lad os nu lave en applet, der viser nogle tegnbare objekter:


import java.applet.*;
import java.awt.*;
import java.util.*;

public class TegnbareObjekter extends Applet
{
  Vector tegnbare = new Vector();
  GrafiskRaflebaeger bæger = new GrafiskRaflebaeger();

  public void paint(Graphics g)
  {
    super.paint(g);
    for (int n=0; n<tegnbare.size(); n++) {
      Tegnbar t = (Tegnbar) tegnbare.elementAt(n);
      t.tegn(g);
    }
  }

  public void sætPositioner()
  {
    for (int n=0; n<tegnbare.size(); n++) {
      Tegnbar t = (Tegnbar) tegnbare.elementAt(n);
      int x = (int) (Math.random()*200);
      int y = (int) (Math.random()*200);
      t.sætPosition(x,y);
    }
  }

  public void init() {
    Stjerne s = new Stjerne();
    tegnbare.addElement(s);
    
    tegnbare.addElement( new Rektangel(10,10,30,30) );

    tegnbare.addElement( new Rektangel(15,15,20,20) );

    GrafiskTerning t;

    t = new GrafiskTerning();
    bæger.tilføj(t);
    tegnbare.addElement(t);

    t = new GrafiskTerning();
    bæger.tilføj(t);
    tegnbare.addElement(t);

    tegnbare.addElement(bæger);

    sætPositioner();

    // mere kode her
    // ... 
  }

  // flere metoder her
  // ... 
}


Programmet holder styr på objekterne i tegnbare-vektoren. Da stjerner, rektangler, terningerne og raflebægeret alle er Tegnbare kan de behandles ens hvad angår tegning og positionering.


11.4 Polymorfi

Det er meget kraftfuldt, at man kan erklære variabler af en interface-type. Disse kan referere til alle mulige slags objekter, der implementerer interfacet. Herefter kan vi f.eks. løbe en vektor igennem og arbejde på objekterne i den, selvom de er af vidt forskellig type.


Dette så vi i TegnbareObjekter-appletten:

    for (int n=0; n<tegnbare.size(); n++)
    {
      Tegnbar t = (Tegnbar) tegnbare.elementAt(n);
      t.tegn(g);
    }


Et interface som Tegnbar kan bruges til at etablere en fællesnævner mellem vidt forskellige objekter, som derefter kan behandles ens. Dette kaldes polymorfi. (græsk: "mange former").


Fællesnævneren - nemlig at de alle implementerer det samme interface - tillader os at arbejde med objekter uden at kende deres præcise type. Dette kan i mange tilfælde være en fordel, når vi arbejder med objekter, hvor vi ikke kender (eller ikke interesserer os for) den eksakte type.


11.5 Interfaces i standardbibliotekerne

Interfaces bliver brugt i vid udstrækning i standardbibliotekerne, og mange steder benyttes polymorfi til at gøre det muligt at lade systemet arbejde på programmørens egne klasser.

I det følgende vil vi se nogle eksempler på at implementationen af et interface fra standardbiblioteket gør, at vores klasser passer ind i systemet på forskellig måde.

11.5.1 Sortering med Comparable

Hvis et objekt implementerer Comparable-interfacet skal det definere metoden:

  public int compareTo(Object obj)


For eksempel:

public class Element implements Comparable
{ 
  int x;

  public Element(int x1)
  {
    x = x1;
  }

  public String toString()
  {
    return "element"+x;
  }

  public int compareTo(Object obj)      // kræves af Comparable
  {
    Element andetElement = (Element) obj;// typekonverter først til Element

    if (x == andetElement.x) return 0;  // dette elem. og obj har samme plads
    if (x  > andetElement.x) return 1;  // dette element kommer efter obj
    else return -1;                // dette element kommer før obj
  }
}


Interfacet giver standardbibliotekerne mulighed for at sammenligne objekter og sortere dem i forhold til hinanden.


Sortering kan bl.a. ske ved at kalde metoden Collections.sort() med en vektor af objekter, der implementerer Comparable.

import java.util.*;
public class BrugElementer
{
  public static void main(String args[])
  {
    Vector liste = new Vector();
    liste.addElement( new Element(5));
    liste.addElement( new Element(3));
    liste.addElement( new Element(13));
    liste.addElement( new Element(1));

    System.out.println("før: "+liste);
    Collections.sort(v);    
    System.out.println("efter: "+liste);
  }
}

før: [element5, element3, element13, element1]
efter: [element1, element3, element5, element13]


sort() vil kalde compareTo() på vores objekter for at ordne dem i rækkefølge. Havde vores objekter ikke implementeret Comparable, ville der opstå en køretidsfejl, da systemet så ikke havde nogen grænseflade, hvorigennem det kunne undersøge, hvordan elementerne skal ordnes.

11.5.2 Flere tråde med Runnable

Hvis man vil bruge flere tråde (processer, der kører samtidigt i baggrunden) i sit program, kan dette opnås ved at implementere interfacet Runnable og definere metoden run(). Derefter opretter man et tråd-objekt med new Thread(objektDerImplementererRunnable). Når tråden startes (med trådobjekt.start()), vil det begynde en parallel udførelse af run()-metoden i objektDerImplementererRunnable.


Dette vil blive behandlet i kapitel 16, Flertrådet programmering.

11.5.3 Lytte til musen med MouseListener

Når man programmerer grafiske brugergrænseflader, kan det være nyttigt at kunne få at vide, når der er sket en hændelse, f.eks. at musen er klikket et sted.


Dette sker ved, at man definerer et objekt (lytteren), der implementerer MouseListener-interfacet. Den har forskellige metoder, f.eks. mouseClicked(), der er beregnet på et museklik.


Lytteren skal registreres i en grafisk komponent, f.eks. en knap eller en applet. Det gøres ved at kalde komponentens addMouseListener()-metode med en reference til lytteren. Derefter vil, hver gang brugeren klikker på komponenten, lytterens mouseClicked() blive kaldt.


Analogt findes lyttere til tastatur, musebevægelser, tekstfelter, kontroller osv. I kapitel 12 om grafiske brugergrænseflader og hændelser er disse ting beskrevet nærmere.


11.6 Test dig selv (fjernet)

Dette afsnit findes i den trykte bog


11.7 Resumé (fjernet)

Dette afsnit findes i den trykte bog


11.8 Opgaver

  1. Lav klassen Hus, der skal implementere Tegnbar, føj den til TegnbareObjekter og prøv om det virker.



11.9 Avanceret (fjernet)

Dette afsnit findes i den trykte bog


1Egentlig signaturen, dvs. metodenavn og antal og type af parametre

2Det vil sige alle objekter, hvis klasse implementerer interfacet.

3public=tilgængelig for alle, static=klassevariabel, final=konstant; umulig at ændre.


Jacob Nordfalk - Objektorienteret programmering i Java - http://javabog.dk