javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

16 Skabende designmønstre


16.1 Overordnet idé 248

16.1.1 Fabrikeringsmetoder 248

16.2 Fabrik 249

16.2.1 Eksempel: Image 249

16.3 Singleton 250

16.3.1 Eksempel: java.lang.Runtime 250

16.3.2 Eksempel: java.awt.Toolkit 250

16.3.3 Eksempel: Dataforbindelse 251

16.3.4 Singleton med sen initialisering 252

16.3.5 Singleton uden privat konstruktør 253

16.4 Abstrakt Fabrik 254

16.4.1 Eksempel: AdresseFabrik 254

16.4.2 Eksempel fra standardklasserne: AWT 256

16.5 Bygmester 257

16.5.1 Trinvis konstruktion 257

16.5.2 Eksempel på trinvis konstruktion: E-post 258

16.5.3 Eksempel på netværk af objekter: Funktioner 258

16.6 Prototype 259

16.6.1 Interfacet Cloneable 259

16.6.2 Overfladisk og dyb kopiering 259

16.6.3 Eksempel: Et tegneprogram 260

16.7 Objektpulje 262

16.7.1 Eksempel: Begrænsede resurser 262

16.7.2 Variation: Klient 'hænger', hvis puljen løber tør 263

16.7.3 Genbrug af tråde 264

16.7.4 Opgave: Objektpulje med objektfabrik 265

16.8 Større eksempel: Dataforbindelse 266

16.8.1 Specialiseringer af Dataforbindelse 267

16.8.2 Dataforbindelse over netværk 269

16.8.3 Dataforbindelse, der cacher forespørgsler 271

16.8.4 Endelig udgave af Dataforbindelse 272

Det er en god idé at kigge i afsnit 1.1, Lister og mængder, kapitel 8, Databaser (JDBC) og afsnit 15.9, Introduktion til designmønstre, før man læser kapitlet.

16.1 Overordnet idé

Den overordnede idé i de skabende designmønstre er at give idéer til, hvordan man afkobler (dvs. mindsker graden af bindinger) mellem den del af programmet, som bruger nogle objekter (kaldet klienten), og den del af programmet, der bestemmer hvordan disse objekter bliver oprettet.

Kig på følgende eksempel, hvor klienten både opretter og bruger objektet af type Hjælp:

  Hjælp h = new Hjælp();        // høj kobling - klient opretter et Hjælp-objekt

  ...

  h.metode1();
  h.metode2();

Ovenstående kan ikke udvides til, at klienten (uden at vide det) f.eks. benytter flere forskellige typer (nedarvinger) af Hjælp-objekter, eller at Hjælp-objektet oprettes med nogle andre parametre i konstruktøren. At oprette et objekt kræver jo, at man angiver præcist, hvilken klasse og hvilken konstruktør der skal anvendes.

Den eneste måde at gøre det på er rent faktisk at ændre i klientens kode - dvs. der er høj kobling (binding).

16.1.1 Fabrikeringsmetoder

En fabrikeringsmetode (eng.: Factory Method) er en metode, der opretter et objekt for klienten. Med en fabrikeringsmetode har man fjernet opgaven med at oprette objektet fra klienten, sådan at klienten i stedet kalder fabrikeringsmetoden.

Eksempelvis kunne Hjælp-klassen have defineret metoden opretHjælp():

  Hjælp h = Hjælp.opretHjælp(); // fabrikeringsmetode leverer objekt til klienten

  ...

  h.metode1();
  h.metode2();

Fabrikeringsmetoder kan være en måde at opnå, at nogle objekter arver fra en fælles superklasse og anvendes ensartet udefra, men internt fungerer forskelligt (polymorfi) uden klientens vidende. Næsten alle de følgende designmønstre gør derfor brug af fabrikeringsmetoder.

Inde i metoden opretHjælp() oprettes objekterne muligvis nøjagtigt lige som før (med new Hjælp()), men det kunne også være at:

16.2 Fabrik

Problem: Klienten kan/skal ikke bestemme præcist, hvordan nogle objekter oprettes.

Løsning: Lad en Fabrik (eng.: Factory) med en fabrikeringsmetode varetage oprettelsen.

En Fabrik er et objekt, der opretter objekter for klienten

En Fabrik er et objekt, der "fabrikerer" objekter for kalderen (klienten), og altså et objekt, der har en fabrikeringsmetode. Den kan lade klienten angive indhold og type. Klienten kender ikke til detaljerne omkring oprettelsen.

16.2.1 Eksempel: Image

Når man vil oprette et billede (af typen Image), sker det ikke med

  Image i = new Image("billede.gif"); // forkert!!

Image-objekter kan være forskelligt repræsenteret afhængig af typen (f.eks. GIF, JPG eller PNG) og derudover bruges Image-objekter også til andre ting, bl.a. filtrerede billeder. Derudover kan et sort/hvid-billede med fordel repræsenteres med en bit per punkt, mens et GIF-billede kan have op til 256 farver og derfor med fordel repræsenteres med en byte per punkt. JPG-billeder kan have op til 16.7 millioner farver. Endelig kan et GIF-billede indeholde gennemsigtige punkter, der ikke tegnes på skærmen, mens et JPG-billede ikke kan være gennemsigtigt.

Det er bedre at lade systemet afgøre præcist, hvilken nedarving af Image der skal oprettes og hvordan. Man kalder derfor i stedet fabrikeringsmetoden getImage() på en grafisk komponent (arving fra java.awt.Component, f.eks. en applet eller et panel):

  Image i = this.getImage("billede.gif"); // i en applet eller panel

Man siger, at komponenten fungerer som en fabrik, og at den fabrikerer Image-objekter.

Image er en abstrakt klasse, der repræsenterer et billede, der kan tegnes på skærmen. Ved hjælp af parameteren afgør metoden getImage(), hvordan billedet skal oprettes: Er parameteren et JPG-billede, vil getImage() vælge en nedarving af Image, der er velegnet til at repræsentere JPG-billeder. Er parameteren et GIF-billede, vil en nedarving velegnet til at repræsentere GIF-billeder blive valgt.

16.3 Singleton

Problem: Klienten må ikke have flere objekter af en bestemt type, men skal altid bruge det samme objekt.

Løsning: Programmér sådan, at der aldrig kan oprettes mere end ét eksemplar af det pågældende objekt.

En Singleton er en klasse, som der må være én og kun én instans (objekt) af

Med en Singleton sørger man altså for, at der eksisterer højst ét objekt. Dette udføres som regel i koden ved at gøre konstruktøren privat og lade klassen selv håndtere oprettelsen af objektet (med en klassemetode, der kan fabrikere objektet).

En Singleton bruges ofte til at repræsentere resurser som i deres natur kun skal eksistere én gang. Det kan for eksempel være en forbindelse til en database, en resurse med begrænset adgang, en systemresurse (f.eks. afspilning af lyd), ...

16.3.1 Eksempel: java.lang.Runtime

Runtime er et eksempel på en Singleton.

Ethvert kørende javaprogram har et objekt af type Runtime, der giver programmet adgang til de omgivelser, det kører i. Man kan f.eks. kalde eksterne programmer, stoppe den virtuelle maskine, undersøge ledig hukommelse, køre garbage collector, indlæse flere klasser og et par andre ting.

Man får fat i Runtime-objektet ved at kalde metoden Runtime.getRuntime().

  Runtime rt = Runtime.getRuntime();

  // eksempler på brug at Runtime-objektet
  System.out.println("Hukommelse reserveret til Java: "+rt.totalMemory());
  System.out.println("Heraf ledigt: "+rt.freeMemory());
  rt.gc(); // kør garbage collector
  System.out.println("Nu ledigt: "+rt.freeMemory());

16.3.2 Eksempel: java.awt.Toolkit

Toolkit-klassen, der repræsenterer de konkrete implementationer af AWT (Abstract Window Toolkit), er et andet eksempel på en singleton. Det er klart at der skal eksistere ét og kun ét grafisk system samtidigt.

Man får fat i Toolkit-objektet med metoden Toolkit.getDefaultToolkit():

  Toolkit tk = Toolkit.getDefaultToolkit();

Fabrikeringsmetoden getDefaultToolkit() returnerer altid det samme objekt (ellers var det ikke en Singleton).

  System.out.println("Skærmstørrelse (punkter): " + tk.getScreenSize());
  tk.beep(); // computeren siger bip
  Image i = tk.getImage("billede.gif");

I den sidste linje fungerer Toolkit-objektet også som fabrik (der fabrikerer billeder).

16.3.3 Eksempel: Dataforbindelse

En forbindelse til en database kan med fordel implementeres som en Singleton, hvis man ønsker, at alle forespørgsler skal gå gennem det samme objekt. Det kunne være for at cache forespørgslerne eller for at sikre konsistens i data.

import java.util.*;

public class Dataforbindelse1
{
  /**
   * Instansen (forekomsten, objektet) af dataforbindelsen.
   * Dette er en klassevariabel, så den oprettes når klassen indlæses.
   */
  private static Dataforbindelse1 instans = new Dataforbindelse1();

  /**
   * Giver instansen af Dataforbindelse.
   */
  public static Dataforbindelse1 hentForbindelse()
  { 
    return instans;
  }

  /**
   * Privat konstruktør sikrer at der ikke kan oprettes objekter udenfor klassen.
   */
  private Dataforbindelse1()
  {
    alle = new ArrayList();
  }

  private List alle;

  public void sletAlleData() { alle.clear(); }
  public void indsæt(Kunde k) { alle.add(k); }
  public List hentAlle() { return alle; }
}

Her er det sikret, at der ikke oprettes flere objekter ved at gøre konstruktøren privat.

Dataforbindelsen bruges fra ens program ved at bede om instansen i stedet for at oprette et nyt objekt:

public class BenytDataforbindelse1
{
  public static void main(String[] args)
  {
    Dataforbindelse1 dbf = Dataforbindelse1.hentForbindelse();
    dbf.indsæt( new Kunde("Kurt",1000) );
    // ...
  }
}

16.3.4 Singleton med sen initialisering

I ovenstående eksempel blev Dataforbindelse-objektet oprettet ved opstart af programmet, når klassen blev indlæst. Har singletonen brug for, at andre ting er initialiseret, før den oprettes, eller er det slet ikke sikkert, at singletonen overhovedet bliver brugt, kan det være hensigtsmæssigt at udskyde oprettelsen af objektet, indtil første gang der er brug for det.

import java.util.*;

public class Dataforbindelse2
{
  /**
   * Instansen (forekomsten, objektet) af dataforbindesen.
   * Dette er en klassevariabel, så den oprettes når klassen indlæses.
   */
  private static Dataforbindelse2 instans = null;

  /**
   * Giver instansen af Dataforbindelse2.
   * Instansen bliver oprettet første gang denne metode kaldes.
   */
  public static synchronized Dataforbindelse2 hentForbindelse()
  {
    if (instans != null) return instans;
    instans = new Dataforbindelse2();
    return instans;
  }

  /**
   * En privat konstruktør sikrer at der ikke kan oprettes objekter uden
   * for klassen.
   */
  private Dataforbindelse2()
  {
    alle = new ArrayList();
  }

  private List alle;

  public void sletAlleData() { alle.clear(); }
  public void indsæt(Kunde k) { alle.add(k); }
  public List hentAlle() { return alle; }
}

Bemærk, at da man kunne være så uheldig, at to tråde samtidig kalder hentForbindelse(), er det nødvendigt, at den er synkroniseret, så trådene kører en ad gangen i metoden.

At kalde en metode, der er synkroniseret, går lidt langsommere, da den virtuelle maskine skal lave noget arbejde for at sikre enkeltradet gennemløb af metoden.

16.3.5 Singleton uden privat konstruktør

Den mest almindelige måde at gennemtvinge en Singleton på er at lave konstruktøren privat. Den har dog det problem, at den forhindrer nedarvinger (f.eks. specialiseringer i forskellige slags datalagre). Det kunne dog løses ved at erklære konstruktøren protected og lægge klassen og dens nedarvinger i en separat pakke (se afsnit 15.7.4, Indkapsling og pakker).

En anden mulighed for at sikre, at der kun er ét objekt (der evt. er en nedarving), er at kaste en undtagelse fra konstruktøren (altså på køretidspunktet at nægte at oprette objektet), hvis objektet allerede eksisterer.

Nedenfor er vist en Dataforbindelse-klasse der, første gang hentForbindelse() bliver kaldt, beslutter om det skal være en Dataforbindelse eller nedarvingen SpecialiseretDataforbindelse, der skal bruges.

import java.util.*;

public class Dataforbindelse3
{
  /**
   * Instansen (forekomsten, objektet) af dataforbindelsen.
   * Dette er en klassevariabel, så den oprettes når klassen indlæses.
   */
  private static Dataforbindelse3 instans;

  /**
   * Giver instansen af Dataforbindelse.
   */
  public static Dataforbindelse3 hentForbindelse()
  { 
    if (instans != null) return instans;

    if ( ...normal brug... ) return new Dataforbindelse3();
    else return new SpecialiseretDataforbindelse();
  }

  /**
   * Konstruktøren sikrer at der ikke kan oprettes flere instanser.
   * Da den ikke er erklæret public er det under alle omstændigheder kun
   * muligt at oprette objekter inden for samme pakke
   * @throws IllegalAccessException hvis instansen allerede eksisterer.
   */
  protected Dataforbindelse3()
  {
    if (instans != null) throw new IllegalAccessException("Objekt eksisterer");
    alle = new ArrayList();
    instans = this;
  }

  private List alle;

  public void sletAlleData() { alle.clear(); }
  public void indsæt(Kunde k) { alle.add(k); }
  public List hentAlle() { return alle; }
}

16.4 Abstrakt Fabrik

Problem: En Fabrik bliver uforholdsmæssigt kompliceret, fordi nogle omstændigheder har stor indflydelse på, hvordan oprettelsen skal foregå.

Løsning: Lav en Abstrakt Fabrik (eng.: Abstract Factory) med en nedarving (Fabrik) for hver omstændighed.

En Abstrakt Fabrik er en Fabrik med nedarvinger, og disse nedarvinger sørger for den egentlige objektoprettelse

En Abstrakt Fabrik kaldes også undertiden Kit eller Toolkit.

Forestil dig, at du har en Fabrik, men at du står i den situation, at der er nogle ydre omstændigheder, der gør, at der nogen gange skal oprettes en anden slags objekter.

Man kunne selvfølgelig klare det med nogle if-sætninger i fabrikeringsmetod(erne), men det bliver bøvlet, hvis der på denne måde kommer et stort antal if-sætninger i hver fabrikeringsmetode, eller hvis der er et stort antal fabrikeringsmetoder.

En smartere løsning er at lade fabrik-klassen være abstrakt og lave en nedarving for hver omstændighed (som man ellers klarede med en if-sætning).

Som regel er det altid den samme nedarvede Fabrik, der skal bruges hver gang. Så bruger man ofte Singleton-designmønstret på superklassen ved at give den en metode til at fremskaffe nedarvingen.

16.4.1 Eksempel: AdresseFabrik

Forestil dig, at du har et program til at lagre internationale telefonnumre og adresser. For hvert land er udformningen af telefonnumre og adresser lidt forskellige. Man kunne lave en fabrik, som indeholdt en masse if-sætninger afhængigt af, hvilket land der var tale om, men det ville give en hel del kode i fabrik-klassen, og hver gang man skal tilføje adresse og telefonnummer for et nyt land, skal man ind og rette i fabrikken.

Så er det bedre at lave Fabrik-klasser, der arver fra en overordnet Fabrik, og som sørger for den specifikke oprettelse af adresse og telefonnummer klasser for hvert land, og så evt. lade den kode, som er generel for alle adresseklasserne, ligge i superklassen.

public class BenytAdresseFabrik {
  public static void main(String[] args)
  {
     AbstraktFabrikIF adresseFabrikRef;

     adresseFabrikRef = new DKAdresseFabrik();
     adresseFabrikRef.opretAdresse();
     adresseFabrikRef.opretTelefonNr();

     // i dette eksempel vælger klienten selv hvilken nedarving den vil bruge.
     adresseFabrikRef = new GRBAdresseFabrik();
     adresseFabrikRef.opretAdresse();
     adresseFabrikRef.opretTelefonNr();
  }
}

Den overordnede Fabrik skal der normalt ikke laves instanser af (den kunne altså erklæres abstrakt - deraf navnet Abstrakt Fabrik). I dette eksempel har vi derfor valgt at lade det være et interface:

public interface AbstraktFabrikIF
{
   public Adresse opretAdresse();
   public TelefonNr opretTelefonNr();
}

public class DKAdresseFabrik implements AbstraktFabrikIF
{
   public Adresse opretAdresse() {
      return new DKAdresse();
   }

   public TelefonNr opretTelefonNr() {
      return new DKTelefonNr();
   }
}
public class GRBAdresseFabrik implements AbstraktFabrikIF
{
   public Adresse opretAdresse() {
      return new GRBAdresse();
   }
   public TelefonNr opretTelefonNr() {
      return new GRBTelefonNr();
   }
}

Adresse-klassen skulle have de metoder og variabler, der forventes at findes i alle adresser:

public abstract class Adresse 
{
  // mangler: metoder/variabler der findes i alle adresser
}
public class DKAdresse extends Adresse 
{
  public DKAdresse() {
     System.out.println("DKAdresse oprettet");
  }
}
public class GRBAdresse extends Adresse
{
  public GRBAdresse() {
     System.out.println("GRBAdresse oprettet");
  }
}

Ligeledes med telefonnumre

public abstract class TelefonNr 
{
  // mangler: metoder/variabler der findes i alle telefonnumre
}
public class GRBTelefonNr extends TelefonNr 
{
  public GRBTelefonNr() {
     System.out.println("GRBTelefonNr oprettet");
  }
}
public class DKTelefonNr extends TelefonNr
{
  public DKTelefonNr() {
     System.out.println("DKTelefonNr oprettet");
  }
}

16.4.2 Eksempel fra standardklasserne: AWT

Klassen Toolkit er et eksempel på en Abstrakt Fabrik.

Da AWT-komponenterne skal virke på flere platforme (Windows, Linux, Solaris, MacOS), fungerer de sådan, at de har en platformsspecifik del (et såkaldt peer) tilknyttet, der sørger for at tegne dem på skærmen.

F.eks. har klassen Button et ButtonPeer-objekt (beskrevet i pakken java.awt.peer), som det delegerer nogle opgaver ud til. Da disse peers er implementeret forskelligt fra platform til platform, er der tilsvarende nedarvinger af ButtonPeer for de forskellige platforme, f.eks. WindowsButtonPeer, LinuxToolkit, SolarisToolkit og MacOSToolkit.

Disse peer-objekter fabrikeres af klassen Toolkit, der har f.eks. metoden createButton til at oprette et ButtonPeer-objekt.

I stedet for, at Toolkit direkte kender til de forskellige styresystemer, er klassen erklæret abstrakt, og der er en nedarving af Toolkit for hvert styresystem (f.eks. WindowsToolkit, LinuxToolkit, SolarisToolkit og MacOSToolkit).

Metoden Toolkit.getDefaultToolkit() giver den konkrete nedarving, der passer til styresystemet.

16.5 Bygmester

Problem: Der skal oprettes flere objekter, som hænger sammen.

Eller problem: De nødvendige informationer til oprettelse af nogle objekter kommer ikke alle på én gang, eller det er uoverskueligt at angive alle informationerne i ét kald.

Løsning: Brug en Bygmester (eng.: Builder) til at specificere informationerne skridt for skridt og til sidst fabrikere objekterne.

Simplificér oprettelsen af nogle relaterede objekter ved at lave en Bygmester-klasse, der opretter og konfigurerer objekterne for klienten

En Bygmester er en Fabrik, der, evt. trinvist, opretter et netværk af objekter. En Bygmester minder også om en Facade (se afsnit 17.4 om Facade), men i stedet for at være en simplificering af brug af nogle objekter er det en simplificering af oprettelsen af nogle objekter.

Ligesom en Fabrik kan en Bygmester lade klienter oprette objekter ved at angive indhold og type. Klienten kender ikke til detaljerne omkring oprettelsen.

16.5.1 Trinvis konstruktion

Bygmesteren kan have metoder til at konfigurere objektet skridt for skridt. Til sidst kaldes en fabrikeringsmetode på bygmesteren, som returnerer det færdige objekt.

Dette er forskelligt fra en Fabrik, hvor konstruktion sker med et enkelt metodekald.

Hvornår bruge trinvis konstruktion?

Trinvis oprettelse kan være berettiget i forskellige situationer:

16.5.2 Eksempel på trinvis konstruktion: E-post

Forestil dig et system, der kan afsende e-post.

Uden en Bygmester skulle klienten kalde en konstruktør med alle parametrene. Konstruktøren kunne f.eks. se således ud:

  public Meddelelse(
    Adresse afsender, 
    Adresse[] modtagere, 
    Date tidspunkt,
    String[] supplerendeData,
    String hovedtekst,
    Vedhaeftning[] vedhæftedeData
  )

Så ville oprettelsen f.eks. se således ud:

  Meddelelse m = new Meddelelse(
    new Adresse("nordfalk@mobilixnet.dk"), 
    new Adresse[] { new Adresse("bo@hansen.dk"), new Adresse("hans@hansen.dk") } 
    new Date(),
    null,
    "En hilsen fra Jacob",
    null
  );

Med en Bygmester sker oprettelsen skridt for skridt, og det er umiddelbart nemmere at forstå, hvad der foregår:

  Meddelelsesbygger mb = new Meddelelsesbygger(); // eller en fabrikeringsmetode

  mb.sætAfsender("nordfalk@mobilixnet.dk");
  mb.tilføjModtager("bo@hansen.dk");
  mb.tilføjModtager("hans@hansen.dk") 
  mb.sætHovedtekst("En hilsen fra Jacob");

  Meddelelse m = mb.byg();

16.5.3 Eksempel på netværk af objekter: Funktioner

I eksemplet afsnit 4.7.1, En komponent til at tegne kurver, har klassen Funktionsfortolker i afsnit 4.7.3 rollen som Bygmester, idet den opretter og konfigurerer Funktion-objekterne til at passe med en bestemt formel.

16.6 Prototype

Problem: Klienten ved ikke, hvad der skal oprettes, men kan dog angive et andet objekt, som ligner det, der skal oprettes.

Løsning: Brug det andet objekt som Prototype, og opret objektet ud fra prototypen.

Tag et eksisterende objekt (prototypen), lav en kopi, og ret kopien til efter behov

Med Prototype kan man lade et objekt oprette en kopi af sig selv på bestilling udefra. Klienten, der bestiller oprettelsen, kan så arbejde med objektet og kopien uden at vide eksakt hvad objektet indeholder eller detaljerne i, hvordan objektet oprettes.

Prototype-designmønstret består altså i at oprette objekter ud fra et allerede eksisterende objekt, hvorefter kopien tilrettes sådan, at den passer til formålet.

Det kan også ske, at klienten beder et andet objekt (en Fabrik eller Bygmester) om at oprette objektet. Klienten angiver så, hvad der skal oprettes, og fabrikken finder så den rette Prototype, kopierer den og retter den til.

Prototype-designmønstret har flere fordele:

16.6.1 Interfacet Cloneable

I Java er Prototype-designmønstret specielt let at implementere, da ethvert objekt har metoden clone() (arvet fra superklassen Object), der returnerer en kopi af objektet. Metoden kan dog kun kaldes på objekter, der tillader det. En klasse kan give tilladelse til at blive klonet ved at den (eller dens superklasse) implementerer interfacet Cloneable.

Ligesom Serializable markerer, at et objekt godt må serialiseres, markerer Cloneable, at et objekt godt må klones (kopieres ved at kalde clone()). Kaldes clone() på et objekt, der ikke implementerer Cloneable, opstår undtagelsen CloneNotSupportedException.

16.6.2 Overfladisk og dyb kopiering

Metoden clone() kopierer objektvariablerne, uanset om det er simple typer eller referencer til andre objekter. Hvis f.eks. et objekt husker en liste af strenge i et Vector-objekt, og vi kalder clone() på objektet for at få en kopi, vil kopien bruge det samme Vector-objekt (da det kun var objektreferencen der blev kopieret), og hvis "den enes" liste ændres, vil "den andens" liste også blive ændret. Dette kaldes en overfladisk kopi (eng.: shallow copy), da kopien har referencer til de samme objekter som originalen.

En dyb kopi (eng.: deep copy) er en kopiering, hvor alle de refererede objekter også kopieres, sådan at originalen og kopien er helt uafhængige og ikke deler objekter. Ønsker man at lave en dyb kopi af et objekt, må man selv skrive koden1.

16.6.3 Eksempel: Et tegneprogram

Forestil dig et program, der kan tegne forskellige former og figurer på skærmen. Programmet har en palette af figurer. Brugeren vælger en figur fra paletten og klikker derefter på skærmen der, hvor den skal være.

Programmet skal altså registrere, hvilken figur brugeren har valgt, og derefter oprette et figur-objekt af den rette type og tegne det på skærmen med de rette koordinater.

En (ikke særlig raffineret) måde at løse denne opgave på er med en række if-sætninger. Hvis der blev klikket på figur 1, så opret et Figur1-objekt, hvis der blev klikket på figur 2, så opret et Figur2-objekt, osv.

I en mere raffineret løsning ville der være en liste af figur-prototyper, og paletten viser alle elementerne i denne liste. Når der klikkes på paletten, findes frem til det pågældende element, og det anvendes som Prototype til at oprette objektet, der skal tegnes på skærmen.

Denne løsning har den fordel, at paletten løbende kan udvides med nye figurer (og derfor er den mere fleksibel end f.eks. en Abstrakt Fabrik). Brugeren kan også tage en eksisterende figur og ændre den (f.eks. farven og om den er udfyldt) og lægge den ind i paletten.

Brugeren kunne måske endda kombinere nogle af figurobjekterne, der tilsammen udgjorde f.eks. et hus eller en mand, i et samlet Figur-objekt (se afsnit 18.5, Komposit/Rekursiv komposition), og udvide paletten med denne figur.

Her er et udkast til koden til eksemplet.

Superklassen Figur implementerer Cloneable og har metoden kopi():

public abstract class Figur implements Cloneable
{
  int x;
  int y;

  public Figur(int x1, int y1)
  {
    x=x1; y=y1;
  }

  /** Tegner figuren på skærmen */
  public abstract void tegn(java.awt.Graphics g);

  /** Giver en overfladisk kopi af dette objekt */
  public Figur kopi() 
  {
    try {
      Figur kopien = (Figur) this.clone();
      return kopien;
    } catch (Exception e) { throw new InternalError("Kunne ikke klone"); }
  }
}

Cirkel har en radius:

public class Cirkel extends Figur
{
  int radius;

  public Cirkel(int x1, int y1, int radius1)
  {
    super(x1,y1);
    radius = radius1;
  }

  public void tegn(java.awt.Graphics g)
  {
    g.drawOval(x,y,radius,radius);
  }
}

Firkant har en bredde og højde:

public class Firkant extends Figur
{
  int bredde;
  int højde;

  public Firkant(int x1, int y1, int bredde1, int højde1)
  {
    super(x1,y1);
    bredde = bredde1;
    højde  = højde1;
  }

  public void tegn(java.awt.Graphics g) 
  {
    g.drawRect(x,y,bredde,højde);
  }
}

Paletten indeholder figurerne og deres navne (til f.eks. at lade brugeren vælge mellem dem i en valgliste). Paletten fungerer som bygmester med metoden opretFigur(), der opretter en figur ud fra prototype og derefter giver kopien de rigtige koordinater.

Metoden tilføj() kan bruges udefra til at føje nye figurer til paletten.

import java.util.*;
public class Palette
{
  /**
   * @associates <{Figur}>
   * @supplierCardinality 0..*
   */
  private List figurer = new ArrayList();
  private List navne = new ArrayList();

  public Palette()
  {
    tilføj("Cirkel", new Cirkel(10,10,10));
    tilføj("Firkant", new Firkant(0,0,20,20));
  }

  public void tilføj(String navn, Figur f)
  {
    navne.add(navn);
    figurer.add(f);
  }

  public List navneliste()
  {
    return navne;
  }

  public Figur opretFigur(int indeks, int x, int y)
  {
    Figur f = (Figur) figurer.get(indeks);
    f = f.kopi();
    f.x = x;
    f.y = y;
    return f;
  }
}

xxx Figurtegning fra

/home/j/dokumenter/vp/dm_eks_prototype_komposit_kommando/Figurtegning.java

ind

16.6.4 Opgave

Definér endnu en figur kaldet xxx

16.7 Objektpulje

Problem: Der er et begrænset antal resurser, som skal fordeles.

Problem: Der oprettes for mange objekter. Programmet er langsomt eller kører ujævnt, fordi der oprettes så mange objekter, der løbende smides væk igen. Objekterne kunne egentligt godt genbruges i stedet for at blive smidt væk, men oprettelserne sker spredt rundt i programmet, så det er svært at koordinere.

Løsning: Lad en Objektpulje (eng.: Object Pool) varetage resurserne/objekterne, og lad klienterne reservere og frigive objekter fra objektpuljen.

Idéen er at genbruge allerede eksisterende objekter, ved at have en pulje med ledige objekter, og på den måde undgå eller begrænse oprettelsen af nye objekter.

Genbrug de samme objekter igen og igen ved at lave en Objektpulje, der husker objekterne

Objektpuljen har en fabrikeringsmetode, der fjerner et objekt fra puljen af ledige objekter og returnerer den. Hvis puljen er tom, oprettes et nyt objekt. Der skal også være en metode til at lægge et objekt, der ikke mere er i brug, tilbage i puljen.

En Objektpulje kan også bruges til at repræsentere begrænsede resurser. I dette tilfælde vil fabrikeringsmetoden, i stedet for at oprette nye objekter, vente, indtil et objekt bliver ledigt, før metoden returnerer (den kaldende tråd bliver altså stoppet).

16.7.1 Eksempel: Begrænsede resurser

Følgende klasse er en meget simpel objektpulje til begrænsede resurser. Hvis puljen løber tør for objekter, kastes en undtagelse.

import java.util.*;
/**
* En pulje af objekter.
* Objekterne skal først føjes til puljen udefra med kald til sætInd()
*/
public class Objektpulje
{
private ArrayList ledige = new ArrayList();

/**
* Læg et ledigt objekt ind i puljen.
* Bruges både til at initialisere puljen lige efter dens oprettelse,
* og løbende, når et fjernet objekt bliver ledigt igen.
*/
public synchronized void sætInd(Object obj)
{
ledige.add(obj);
}
/**
* Tag et ledigt objekt ud af puljen.
* @throws RuntimeException hvis puljen er løbet tør for objekter.
*/
public synchronized Object tagUd()
{
if (ledige.isEmpty()) throw new RuntimeException("Ikke flere objekter");
Object obj = ledige.remove(ledige.size()-1); // tag objekt ud af puljen
return obj;
}
}

16.7.2 Variation: Klient 'hænger', hvis puljen løber tør

I nogle tilfælde er det mere hensigtsmæssigt, at objektpuljen lader kalderen vente på, at der bliver et objekt ledigt. Det følgende eksempel er en objektpulje til begrænsede resurser, hvor den kaldende tråd, hvis puljen er tom, 'hænger', indtil der kommer et ledigt objekt.

import java.util.*;
/**
* En pulje af objekter.
* Objekterne skal tilføjes til puljen udefra.
*/
public class ObjektpuljeKlientHaenger
{
private ArrayList ledige = new ArrayList();

/**
* Læg et ledigt objekt ind i puljen.
* Bruges både til at initialisere puljen lige efter dens oprettelse,
* og løbende, når et fjernet objekt bliver ledigt igen.
*/
public synchronized void sætInd(Object obj)
{
ledige.add(obj);
this.notify(); // væk eventuelle ventende tråde
}

/**
* Tag et ledigt objekt ud af puljen.
* Er der ikke flere objekter tilbage 'hænger' kaldet, indtil
* et objekt bliver ledigt.
*/
public synchronized Object tagUd()
{
try {
while (ledige.isEmpty()) // så længe der ikke er ledige objekter...
{
System.out.println("Ikke flere objekter i puljen, venter...");
this.wait(); // .... vent på at blive vækket
}
Object obj = ledige.remove(ledige.size()-1); // tag objekt
return obj;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
}

16.7.3 Genbrug af tråde

At oprette en tråd er dyrt i kørselstid. Ønsker man derfor et hurtigt system, bør man genbruge tråde i stedet for at oprette og nedlægge dem alt for ofte.

Det følgende eksempel viser, hvordan en pulje af tråde kan implementeres. En tråd aktiveres ved at kalde metoden startOpgave() med et objekt, der så vil få kaldt sin run()-metode i en separat arbejds-tråd (er der ingen ledige tråde, oprettes en ny).

import java.util.*;

public class Traadpulje
{
  private List ledige = new ArrayList(); // Listen over ledige arbejdstråde

  /** Læg en opgave i kø til en arbejdstråd */
  public synchronized void startOpgave(Runnable opgave)
  {
    Arbejder a;

    if (ledige.isEmpty())
    {
      a = new Arbejder(); // ingen arbejdstråde ledige, en ny oprettes
      a.setDaemon(true);  // tillad systemet at lukke ned selvom tråden er aktiv
      System.out.println("Ny arbejdstråd oprettet.");
      a.start();
    } else synchronized(ledige) {
      a = (Arbejder) ledige.remove(ledige.size()-1);// tag arbejdstråd fra liste
    }

    synchronized(a)
    {
      if (a.opgave != null) throw new InternalError("Tråden kører allerede");
      a.opgave = opgave;
      a.notify();         // væk arbejdstråden der venter i wait()
    }
  }

  /**
   * Arbejds-tråden.
   * Den er stærkt bundet til puljen så den er lagt som en privat indre klasse.
   */
  private class Arbejder extends Thread
  {
    private Runnable opgave = null;

    public final synchronized void run()
    {
      while (true) try
      {
        if (opgave != null)
        {
          System.out.println(this+" virker nu på "+opgave);
          opgave.run();      // udfør opgaven
          opgave = null;     // ... og glem den (!)
          synchronized(ledige) {ledige.add(this);} // læg tråd tilbage i listen 
        }
        System.out.println(this+" venter på opgave.");
        this.wait();         // vent på at blive vækket med notify()
      } catch (Exception e) {
        System.err.println(this+": Fejl opstod i opgave "+opgave);
        e.printStackTrace();
      }
    } // slut på run()
  }  // slut på den indre klasse
}

Trådpuljen kunne for eksempel udnyttes i en webserver (her er den anvendt på eksemplet FlertraadetHjemmesidevaert fra kapitel 17 i http://javabog.dk):

import java.io.*;
import java.net.*;
import java.util.*;

public class FlertraadetHjemmesidevaertMedTraadpulje
{
public static void main(String arg[])
{
try {
ServerSocket værtssokkel = new ServerSocket(8001);

Traadpulje trådpulje = new Traadpulje(); // nyt
while (true)
{
Socket forbindelse = værtssokkel.accept();
Anmodning a = new Anmodning(forbindelse);

trådpulje.startOpgave(a); // nyt
// før: new Thread(a).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
} class Anmodning implements Runnable { private Socket forbindelse; Anmodning(Socket forbindelse) { this.forbindelse = forbindelse; } public void run() { try { PrintWriter ud = new PrintWriter(forbindelse.getOutputStream()); BufferedReader ind = new BufferedReader( new InputStreamReader(forbindelse.getInputStream())); String anmodning = ind.readLine(); System.out.println("start "+new Date()+" "+anmodning); ud.println("HTTP/0.9 200 OK"); ud.println(); ud.println("<html><head><title>Svar</title></head>"); ud.println("<body><h1>Svar</h1>"); ud.println("Tænker over "+anmodning+"<br>"); for (int i=0; i<100; i++) { ud.print(".<br>"); ud.flush(); Thread.sleep(100); } ud.println("Nu har jeg tænkt færdig!</body></html>"); ud.flush(); forbindelse.close(); System.out.println("slut "+new Date()+" "+anmodning); } catch (Exception e) { e.printStackTrace(); } } }

16.7.4 Opgave: Objektpulje med objektfabrik

Udvid Objektpulje, sådan at den kan få overført en Objektfabrik i konstruktøren. Klassen Objektfabrik har metoden opretObjekt(), der skaber nye objekter til puljen. Ændr puljen til at bruge fabrikken, sådan at der oprettes nye objekter, hvis puljen løber tør.

16.8 Større eksempel: Dataforbindelse

En forbindelse til en database kan med fordel implementeres som en Singleton, hvis man ønsker, at alle forespørgsler skal gå gennem det samme objekt. Det kunne være for at cache forespørgslerne eller for at sikre konsistens i data.

Det følgende er også et eksempel på indkapsling og abstraktion - al SQL-specifik kode er pakket ind i en klasse for sig, i et Dataforbindelse-objekt.

For at abstraktionen skal holde, skal de data (her drejer det sig om kunder), der overføres mellem dataforbindelsen og klientprogrammet, også repræsenteres på en måde, så de ikke er afhængige af, at kommunikationen skal foregå med JDBC til en SQL-database:

import java.io.*;

public class Kunde implements Serializable
{
  public String navn;
  public double kredit;

  public Kunde(String n, double k)
  {
    navn = n;
    kredit = k;
  }

  public String toString() { return navn+": "+kredit+" kr."; }
}

Klient-programmet kan så abstrahere fra, hvordan data er lagret:

import java.util.*;

public class BenytDataforbindelse
{
  public static void main(String arg[])
  {
    try {
      Dataforbindelse dbf = Dataforbindelse.hentForbindelse();

      List liste = dbf.hentAlle();
      System.out.println("Alle data: "+ liste);
      dbf.sletAlleData();

      System.out.println("Alle data nu: "+dbf.hentAlle());

      dbf.indsæt( new Kunde("Kurt",1000) );
      dbf.indsæt( new Kunde("kunde indsat fra BenytDataforbindelse", 2) );
      System.out.println("Alle data nu: "+dbf.hentAlle());
    } catch(Exception e) {
      System.out.println("Problem med dataforbindelse: "+e);
      e.printStackTrace();
    }
  }
}

Alle data: [Kurt: 1000.0 kr., kunde indsat fra BenytDataforbindelse: 2.0 kr.]
Alle data nu: []
Alle data nu: [Kurt: 1000.0 kr., kunde indsat fra BenytDataforbindelse: 2.0 kr.]

Herunder er Dataforbindelse programmeret som en Singleton (noget af koden er skåret væk).

import java.util.*;

public abstract class Dataforbindelse
{
  abstract public void sletAlleData() throws Exception;
  abstract public void indsæt(Kunde k) throws Exception;
  abstract public List hentAlle() throws Exception;

  // fabrikeringsmetode
  synchronized public static Dataforbindelse hentForbindelse() throws Exception
  {
    // - her implementeret som en Singleton

    if (forb != null) return forb;
    // ... her skal kode ind der skaffer en forbindelse

    forb = new DataforbindelseDummy();    // lige nu
    //forb = new DataforbindelseOracle(); // senere
    //forb = new DataforbindelseOverNetvaerketTilEnServlet(); // endnu senere!!

    return forb;
  }
  private static Dataforbindelse forb;
}

Fabrikeringsmetoden Dataforbindelse.hentForbindelse() kan senere ændres til at skaffe andre slags forbindelser, uden at der skal rettes i klient-programmet.

16.8.1 Specialiseringer af Dataforbindelse

Der kan være forskellige specialiseringer af Dataforbindelse til forskellige situationer:

I starten af programmeringen udvikles nok en DataforbindelseDummy (eller DataforbindelseStub), der kun er beregnet til test. Den lader, som om den er en forbindelse, men gør reelt ingen verdens ting:

import java.util.*;

public class DataforbindelseDummy extends Dataforbindelse
{
  public void sletAlleData()  { System.out.println("sletAlleData() kaldt"); }
  public void indsæt(Kunde k) { System.out.println("indsæt("+k+") kaldt"); }

  public List hentAlle()
  {
    List alle = new ArrayList();
    Kunde k = new Kunde( "Jacob", -1722);
    alle.add(k);
    return alle;
  }
}

I denne udgave har vi ikke en gang gidet at huske data (selvom det ville være nemt nok i dette tilfælde).

Senere laves en, der husker data, men serialiseret i en fil:

import java.util.*;
import java.io.*;
public class DataforbindelseFil extends Dataforbindelse {
  private List alle;

  private void gem() {
    try {
      ObjectOutputStream p = new ObjectOutputStream(
        new FileOutputStream("data.ser"));
      p.writeObject(alle);
      p.close();
    } catch (Exception e) { e.printStackTrace(); }
  }

  public DataforbindelseFil() {
    try {
      ObjectInputStream p = new ObjectInputStream(
        new FileInputStream("data.ser"));
      alle = (List) p.readObject();
      p.close();
    } catch (Exception e) {
      alle = new ArrayList();
    }
  }

  public void sletAlleData() {
    alle = new ArrayList();
    gem();
  }

  public void indsæt(Kunde k) {
    alle.add(k);
    gem();
  }

  public List hentAlle() { return alle; }
}

Senere, når de rigtige tabeller osv. er blevet oprettet i databasen, laves en rigtig forbindelse:

import java.sql.*;
import java.util.*;
public class DataforbindelseOracle extends Dataforbindelse {
  private Connection forb;
  private Statement stmt;

  public DataforbindelseOracle() throws Exception {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    Connection forb = DriverManager.getConnection(
      "jdbc:oracle:thin:@ora.javabog.dk:1521:student","stuk1001","hemli'");
    stmt = forb.createStatement();
  }

  public void sletAlleData() throws SQLException {
    stmt.execute("truncate table kunder");
  }

  public void indsæt(Kunde k) throws SQLException {
    stmt.executeUpdate(
      "INSERT INTO kunder VALUES('" + k.navn + "', " + k.kredit + ")");
  }

  public List hentAlle() throws SQLException {
    List alle = new ArrayList();
    ResultSet rs = stmt.executeQuery("SELECT navn, kredit FROM kunder");
    while (rs.next())
    {
      Kunde k = new Kunde( rs.getString("navn"), rs.getDouble("kredit"));
      alle.add(k);
    }
    return alle;
  }
}

16.8.2 Dataforbindelse over netværk

Endnu senere kan det være, at programmet er udvidet med en forbindelse, der kontakter en servlet på en webserver et andet sted på netværket. Altså et objekt, der 'lader som om', det er det rigtige objekt (en Dataforbindelse), men i virkeligheden sender kaldene videre til det rigtige Dataforbindelse-objekt (der i dette tilfælde ligger på en anden maskine). Dette kaldes også en Proxy - se afsnit 17.1.

Herunder er sådan en forbindelse implementeret. Den sender en GET-anmodning med HTTP-protokollen v.hj.a. URL-klassen. Med anmodningen sendes parameteren kommando, der beskriver, hvad der skal gøres. Eventuelle data indkodes med URLEncoder-klassen (i metoden indsæt()). For eksempel kunne en anmodning om at slette alle data se således ud:

http://localhost:8080/servlet/DataforbindelseServlet?kommando=sletAlleData

De fleste kommandoer forventer ikke noget svar. Eneste undtagelse er kommando=hentAlle, der forventer, at servletten sender binære data tilbage i form af serialiserede objekter.

import java.util.*;
import java.net.*;
import java.io.*;

/**
 * Dataforbindelse over netværket til en servlet.
 * @see DataforbindelseServlet
 */
public class DataforbindelseOverNetvaerketTilEnServlet extends Dataforbindelse
{
  private String basisUrl;

  private InputStream spørg(String spm) throws IOException
  {
    System.out.println("Spørger på "+ basisUrl+"?"+spm);
    URL u = new URL(basisUrl+"?"+spm);// opret URL
    u.openConnection().connect();     // send forespørgslen
    return u.openStream();            // returner datastrøm med svaret
  }

  public DataforbindelseOverNetvaerketTilEnServlet(String urlPåServlet)
  {
    basisUrl = urlPåServlet;
  }

  public void sletAlleData() throws IOException
  {
    spørg("kommando=sletAlleData");
  }

  public void indsæt(Kunde k) throws IOException
  {
    spørg("kommando=indsæt"
      +"&navn="   +URLEncoder.encode(k.navn)        // indkod navn 
      +"&kredit=" +URLEncoder.encode(""+k.kredit)); // indkode kredit
  }

  public List hentAlle() throws Exception
  {
    InputStream is = spørg("kommando=hentAlle");
    ObjectInputStream p = new ObjectInputStream(is);
    List alle = (List) p.readObject();              // deserialisér liste-objekt
    p.close();

    return alle;
  }
}

Herunder er den tilsvarende servlet. Den bruger et dataforbindelses-objekt på værten til at afgøre, hvad den skal svare. Her er det vigtigt at denne Dataforbindelse på værtsmaskinen ikke selv forsøger at få data fra en servlet (data skal jo komme et sted fra i sidste ende).

Derfor giver den Dataforbindelse et vink (med Dataforbindelse.sætForbindelsesvink()) om hvilken forbindelsestype den ønsker.

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;

public class DataforbindelseServlet extends HttpServlet {

  public void init(ServletConfig config) throws ServletException {
    super.init(config);

    // Det er vigtigt at dataforbindelsen på værten IKKE forsøger
    // at få forbindelse med en servlet, så vi sætter et vink om
    // hvilken forbindelse vi ønsker
    try {
      Dataforbindelse.sætForbindelsesvink("fil");
    } catch (Exception e) { e.printStackTrace(); }
  }

  public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    try {
      String kommando = request.getParameter("kommando");
      Dataforbindelse dbforb = Dataforbindelse.hentForbindelse();

      System.out.println("kommando="+kommando);
      // Test - for at sikre os at det rent faktisk kommer gennem servletten
      // sender vi en ekstra "kunde" med
      // dbf.indsæt( new Kunde( "Servlet kommando = "+kommando, 0) );

      if ("sletAlleData".equals(kommando)) dbforb.sletAlleData();
      else if ("hentAlle".equals(kommando))
      {
        // sæt indholdstypen til noget binært (bare noget andet end text/html
        response.setContentType("application/x-serialiserede-kunder");

        // serialisér svaret og send det til klienten
        List alle = dbforb.hentAlle();
        ObjectOutputStream p=new ObjectOutputStream(
          response.getOutputStream());
        p.writeObject(alle);
        p.close();
        return;
      }
      else if ("indsæt".equals(kommando)) {
        Kunde k = new Kunde (
          request.getParameter("navn"),
          Double.parseDouble( request.getParameter("kredit") )
        );
        dbforb.indsæt(k);
      } else throw new IllegalArgumentException("ukendt kommando: "+kommando);

    } catch (Exception e) {
      e.printStackTrace();
      response.setContentType("text/html");
      PrintWriter out = response.getWriter();
      System.out.println();
      out.println("<html>");
      out.println("<head><title>DataforbindelseServlet - fejl</title></head>");
      out.println("<body><h1>Fejl: "+e+"</h1>");
      out.println("<p>Der opstod en fejl i servletten:</p>");
      e.printStackTrace(out);
      out.println("</body></html>");
    }
  }
}

Teknikken med at serialisere objekter og sende dem over netværket over HTTP anvendes ofte til at sende objekter til en applet.

16.8.3 Dataforbindelse, der cacher forespørgsler

Efterhånden som programmet bliver mere og mere udviklet, opdager vi måske, at nogle dele af programmet, der laver hyppige dataforespørgsler, kører for langsomt.

De fleste af forespørgslerne er endda overflødige, da data ikke har ændret sig.

Nu kunne vi selvfølgelig kode DataforbindelseOverNetvaerketTilEnServlet om til også at cache forespørgslerne, men det ville give en lavere kohæsion (diskuteret i afsnit 15.7.2, Høj kohæsion), da klassen så ville have to ansvarsområder (forespørgsler over netværket og caching).

En mere elegant løsning ville være at lave en klasse, der kun tog sig af caching. Den kunne lade som om, den var en Dataforbindelse, men i virkeligheden kalder den videre i en anden Dataforbindelse, hvis den ikke har svaret (dette kaldes også en Proxy - se afsnit 17.1).

import java.util.*;

public class DataforbindelseCache extends Dataforbindelse
{
  private Dataforbindelse df;
  private List cache;

  public DataforbindelseCache(Dataforbindelse forb) { 
    df = forb;
  }

  public void sletAlleData() throws Exception
  {
    cache = null;            // der er sket en ændring - nulstil cache
    df.sletAlleData();
  }

  public void indsæt(Kunde k) throws Exception
  {
    cache = null;            // der er sket en ændring - nulstil cache
    df.indsæt(k);
  }

  public List hentAlle() throws Exception
  {
    if (cache != null)
    {
      // Vi har listen i cachen - returnér den, uden at spørge videre
      return cache;
    } 
    else 
    {
      // Øv - vi har ikke listen i cachen - vi er nødt til at spørge videre
      cache = df.hentAlle(); // husk listen til en anden gang
      return cache;
    }
  }
}

16.8.4 Endelig udgave af Dataforbindelse

Her er Dataforbindelse.java, som den ser ud sidst i programmeringsforløbet:

import java.util.*;

public abstract class Dataforbindelse
{
  /**
   * Sletter alle data
   */
  abstract public void sletAlleData() throws Exception;

  /**
   * Indsætter en kunde
   * @param kunden
   */
  abstract public void indsæt(Kunde k) throws Exception;

  /**
   * Henter alle kunderne
   * @return en liste af Kunde-objekter
   */
  abstract public List hentAlle() throws Exception;

  private static Dataforbindelse forb;
  /**
   * Fabrikeringsmetode til at skaffe en forbindelse
   * @return forbindelsen
   */
  synchronized public static Dataforbindelse hentForbindelse() throws Exception
  {
    // implementeret som en Singleton, så der må kun eksistere ét objekt
    if (forb != null) return forb;

    // ... herunder skal kode ind der skaffer en forbindelse
    //forb = new DataforbindelseDummy();  // før
    //forb = new DataforbindelseFil();    // senere
    forb = new DataforbindelseOracle();
    // endnu senere !!!
    // forb = new DataforbindelseOverNetvaerketTilEnServlet(
    //   "http://localhost:8080/servlet/DataforbindelseServlet");

    return forb;
  }

  /**
   * Lavet sådan at man kan angive hvilken slags forbindelse man foretrækker.
   * Dette er rart til afprøvning og fejlfinding.
   * Skal kaldes som <b>før</b> første kald til <code>hentForbindelse()</code>.
   * Eksempler på brug:<br>
   * <code>Dataforbindelse.sætForbindelsesvink("dummy");</code><br>
   * <code>Dataforbindelse.sætForbindelsesvink("fil");</code><br>
   * <code>Dataforbindelse.sætForbindelsesvink(
   *   "http://localhost:8080/servlet/DataforbindelseServlet");</code>
   *
   * @param vink Vink til hvilken type forbindelse der foretrækkes
   * @see #hentForbindelse()
   */
  synchronized public static void sætForbindelsesvink(String v) throws Exception
  {
    if (forb != null) return; // ignorer vink når forbindelse først er oprettet.
    if (v.equals("dummy"))         forb = new DataforbindelseDummy();
    else if (v.startsWith("fil"))  forb = new DataforbindelseFil();
    else if (v.startsWith("http")) forb = new DataforbindelseCache(
              new DataforbindelseOverNetvaerketTilEnServlet(v));    // brug cache
    else forb = new DataforbindelseOracle();
  }
}

Bemærk, at vi ikke smider de tidlige udgaver, f.eks. DataforbindelseDummy, ud. De kan måske være nyttige senere, f.eks. er det ikke utænkeligt, at man senere skal videreudvikle en anden del af programmet, men af den ene eller anden grund ikke kan/vil koble op til database.

1En mulighed er dog at bruge serialisering - serialisere objektet til en datastrøm (en fil eller et array af byte) og deserialisere det igen. Dette er dog ikke nogen særlig effektiv måde at lave kopier af objekter, så den anbefales ikke, hvis programmet skal køre hurtigt, eller der skal kopieres mange objekter.

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (71% af værket).

Ønsker du at se de sidste 29% af dette værk (362838 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.