© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Figurer

Opgaver

 

 

Når man skal specialisere JHotDraw, består en væsentlig del af opgaven i at lave egne figurer. Dette gøres ved at lave subklasser af diverse figur-klasser i JHotDraw.
Der findes en hel del af disse klasser:
Figur 1:
Figure-klasser i JHotDraw
[Jeg skal have ændret CompositeFigure til at være abstrakt - dog ikke grå, da de tre grå klasser illustrerer grundstammen i nedarvningshierarkiet, og ikke at de er abstrakte]
Ud over at de alle implementerer Figure-interfacet, bemærker man, at de kredser omkring de tre abstrakte klasser: AbstractFigure, AttributeFigure og CompositeFigure.

 

1. AttributeFigure

Kan beskrives med attributter Man bemærker ligeledes, at langt de fleste figurer nedarver fra AttributeFigure. At en figur er en AttributeFigure, betyder at deres egenskaber kan beskrives med en række værdier. Dette er naturligvis meget generelt og burde i princippet betyde at alle figurer var AttributeFigure's. Grunden til at det ikke er tilfældet, skyldes spørgsmålet om hvordan disse attributters værdier opbevares.
Container Klassen AttributeFigure har en container som den stiller til rådighed for sine subklasser. Denne container tilgås med følgende service-metoder:
Object getAttribute( String name )
void setAttribute( String name, Object value )
Enkeltstående værdier Denne container retter sig primært mod egenskaber der kan beskrives med enkeltstående værdier. Har man i stedet en række ensartede værdier, er containeren knap så bekvem. Dette gør sig f.eks. gældende for PolyLineFigure; hvis attributter er en række af punkter. Det er derfor mere bekvemt for PolyLineFigure at lave sin egen container, i stedet for at bruge AttributeFigure's.
AttributeFigure har også en statisk metode, der kan bruges til at få defaultværdier for en række egenskaber.
Object getDefaultAttribute( String name )
Default-attributterne er initialiseret til:
Navn
Værdi
FrameColor Color.black
FillColor new Color( 0x70DB93 )
TextColor Color.black
ArrowMode new Integer( 0 )
FontName "Helvetica"
FontSize new Integer( 12 )
FontStyle new Integer( Font.PLAIN )
Disse default-værdier er fælles for alle AttributeFigure's, og sættes der ikke andre værdier for de enkelte instanser af subklasser til AttributeFigure, er det dem der er gældende når man kalder getAttribute. getAttribute-metoden bruger nemlig default-værdierne når den ikke kan finde en værdi for attributten i den pågældende figur.

 

1.1 Drawing

Ud over attribut-egenskaben, har en AttributeFigure to get-metoder, der blot er interface-sukker for to getAttribute-kald:
Color getFillColor()
Color getFrameColor()
Disse metoder kalder blot getAttribute med parameteren: "FillColor" henholdsvis "FrameColor".
At disse metoder findes skal ses i lyset af, at AttributeFigure implementerer draw-metoden fra Figure-interfacet, således at den kalder to metoder: drawBackground og drawFrame. Disse metoder laver henholdsvis baggrunden og rammen for den figur der tegnes, og anvender netop de to nævnte farver.
I relation til Template Method, bemærker man at draw fungerer som en template-metode, og de to andre metoder optræder som hook-metoder. Subklasser til AttributeFigure kan derfor nøjes med at override drawBackground og drawFrame.

 

2. AbstractFigure

Attribut-egenskab!? En pudsighed ved AbstractFigure, som jeg vil tillade mig at stille spørgsmålstegn ved fornuften i, er at AbstractFigure har de to metoder: getAttribute og setAttribute i sit interface. Det virker begrebsmæssigt inkonsistent! Det skal bemærkes at metoderne er tomme, og derfor ignoreres forsøg på at anvende attributter. Logisk nok, da der ikke er tale om en AttributeFigure - men hvorfor skal de så være i interfacet? Dette stammer endog fra Figure-interfacet!
Default-implementation Formålet med AbstractFigure er at give en default-implementation af en række af metoderne fra Figure-interfacet til subklasser, så disse ikke behøver at implementere dem alle. Vi vil i det følgende se nærmere på en del af disse metoder, og hvordan subklasser skal forholde sig til dem.

 

2.1 Flytning af figuren

Man flytter en figur ved at kalde:
void moveBy( int dx, int dy )
Som parameter angives den relative ændring i figurens position.
I relation til Template Method, er der tale om en template-metode, der kalder hook-metoden: basicMoveBy. Man skal bemærke at sidst nævnte metode ikke er implementeret, og det påhviler derfor subklasser at realisere selve flytningen af figuren.
Laver man en subklasse, til en af AttributeFigure's subklasser, kalder man i starten af basicMoveBy, super-klassens implementation. På den måde skal man ikke selv flytte figuren, men kan i stedet bruge metoden til at registrere, og evt. reagere på, at figuren er blevet flyttet (se evt. opgave 1, til dette kapitel):
public void basicMoveBy( int dx, int dy ) {
  super.basicMoveBy( dx, dy );


  // vi kan konstatere at figuren er blevet flyttet!
}

 

2.2 Resize af figuren

Man ændrer størrelsen på en figur med en af følgende metoder:
void displayBox( Point origin, Point size )
void displayBox( Rectangle r )
Den sidste af disse metoder er blot interface-sukker for den første.
Som man ser, kan man ikke ændre en figurs størrelse uden samtidig at skulle angive dens placering. Ønsker man kun at ændre størrelsen af figuren, bliver man derfor nød til at spørge den om dens placering, og dernæst fortælle den det igen!
Man kan få figuren placering (og størrelse) ved et kald af metoden:
Rectangle displayBox()
Det skal bemærkes at denne metode ikke er implenteret i AbstractFigure.
Man kunne måske så undre sig over, hvad implementationen af de to set-metoder gør - for de er implementeret! De fører til et kald af:
void basicDisplayBox( Point origin, Point size )
som ikke er implementeret i AbstractFigure.
Idéen er at anvende Template Method, så det overlades til sub-klasser hvad der skal ske. Laver man derfor en subklasse til en af AttributeFigure's subklasser, skal man (som det var tilfældet med basicMoveBy ovenfor) kaldes superklassens implementation. På denne måde gør hook-metoden: basicDisplayBox, det muligt at registrere, og evt. reagere på, at figuren ændrer størrelse (og placering):
public void basicDisplayBox( Point origin, Point size ) {
  super.basicDisplayBox( origin, size );


  // vi kan konstatere at figuren har ændret størrelse
}

 

3. ConnectionFigure

Vi vil i det følgende se nærmere på LineConnection-klassen, der implementerer ConnectionFigure-interfacet (vi vil i det følgende løst betegne instanser af klasser, der implementerer dette interface, som connections). ConnectionFigure-interface indeholder hele 20 metoder, men vi vil begrænse os til kun at se på de mest interessante af dem.
LineConnection's er en speciel form for figurer, der anvendes til at forbinde andre figurer. I diagrammer bruger man ofte diverse former for linier til at beskrive relationer mellem "ting" - f.eks. nedarvning mellem klasser; hvor "nedarvnings-pilen" vil være en LineConnection og klasserne vil være almindelige figurer (f.eks. GraphicalCompositeFigure's, som i Wolfram Kaiser's JModeller-eksempel).
Lad os lave en simpel LineConnection, og se hvilke muligheder der er.
Vores LineConnection skal som udgangspunkt kun være en rød streg. Vi laver derfor en subklasse: VorLineConnection til LineConnection-klassen:
VorLineConnection.java
import java.awt.*;

import CH.ifa.draw.figures.*;

public class VorLineConnection extends LineConnection {
  
  public void drawLine( Graphics g, int x1, int y1, int x2, int y2 ) {
    g.setColor( Color.red );
    g.drawLine( x1, y1, x2, y2 );
  }
}
drawLine-metoden bliver kaldt når en connection skal tegnes, og man får til formålet en Graphics, samt start- og slut position for linien i realtion til denne Graphics. Hvad man reelt tegner er op til én selv, men det er naturligvis tanken, at det skal være en slags linie. I vores eksempel vælger vi at tegne en rød linie.
Med denne klasse kan vi nu lave følgende connection, mellem f.eks. to RectangleFigure's:
Som ventet bliver linien rød, men hvor kommer de to pile-spidser fra?
Det er muligt at knytte en LineDecoration til hver ende af en LineConnection. Default bliver disse sat til at være pile-spidser (instanser af ArrowTip). Man kan selv sætte disse LineDecoration's til noget andet med metoderne:
void setStartDecoration( LineDecoration decoration )
void setEndDecoration( LineDecoration decoration )
Default LineDecoration's sættes i LineConnection-klassens default konstruktor, og det er derfor bekvemt at sætte disse til noget andet i ens egen konstruktor, såfremt man ønsker andre. I vores eksempel ønsker vi ikke nogen LineDecorations; hvorfor vi sætter dem begge til null:
public VorLineConnection() {
  setStartDecoration( null );
  setEndDecoration( null );
}
Som udgangspunkt kan en connection forbinde alle figurer der ikke selv er LineConnection's, men vi har mulighed for selv at bestemme hvilke figurer vi som connection vil acceptere at forbinde.
Vi vil i den forbindelse nævne følgende tre metoder:
boolean canConnect( Figure start, Figure end )
void handleConnect( Figure start, Figure end )
void handleDisconnect( Figure start, Figure end )
Den første metode bestemmer hvilke figurer vi som connection vil acceptere at forbinde, mens de to andre håndterer den event, at vi nu forbinder to figurer, henholdsvis ikke længere forbinder dem.
Øvelse: Lav implementationer af disse tre metoder i VorLineConnection, således at vi kun vil acceptere at forbinde to RectangleFigure's, og at de to metoder til eventhåndtering blot udskriver hændelsen. Eksperimenter ved at prøve at forbinde diverse figurer, og flytte og fjerne forbindelser. Bemærk hvornår de forskellige metoder kaldes.