Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Objekt-orienteret programmering i Java, del 2


4.1   Programmering af klassen Rectangle  

Denne opgave går ud på at programmere en klasse Rectangle. (En sådan klasse findes i forvejen i Java klassebiblioteket, men dette ignorerer vi i denne opgave). Et rektangel er et velkendt geometrisk objekt med en bestemt placering i et todimensionelt koordinatsystem. I denne opgave vil vi afgrænse os til at arbejde med rektangler, hvis sider er parallelle med akserne i koordinatsystemet. Antag også at vi arbejder med heltallige koordinater og dimensioner.

Start med at beslutte hvordan I vil repræsentere et rektangel. Datarepræsentationen skal være skjult for klienter af rektanglet.

Implementer følgende konstruktorer og metoder i Rectangle:

    En konstruktor som opretter et rektangel ud fra to hjørnepunkter: øverste venstre og nederste højre. En konstruktor som opretter et rektangel ud fra øverste venstre hjørne samt en bredde og en højde af rektanglet. En konstruktor som opretter et rektangel ud fra fire heltal: x og y koordinaterne af øverste venstre samt nederte højre hjørne Metoder width og height som returnerer bredde og højde af rektanglet En metode corners som returnerer et array af de fire hjørnepunkter af rektanglet En metode move der flytter rektanglet. Flytningen skal være relativ til den nuværende position. En metode overlappingRectangle der returnerer overlappet (i sig selv et rektangel) mellem to rektangler (se herunder). Metoden toString som udskriver en tekstuel og informativ streng om rektanglet
Under løsningen af denne opgave bedes I benytte klassen java.awt.Point som beskriver et punkt i den todimensionelle plan.

Det vanskeligste aspekt af klassen Rectangle er metoden overlappingRectangle. Det kan være en god hjælp at bruge klassen Interval (udviklet til formålet) hvori der findes en metode, der returnerer overlappet mellem to intervaller.

Dokumenter endvidere klassens konstruktorer og metoder, og producer HTML dokumentation ved brug af værktøjet javadoc.

Solution
Her følger en løsning på opgaven:

/** A clonable Point */
class Point extends java.awt.Point implements Cloneable {
  
  public Object clone(){
    return (super.clone());
  } // end clone

  public Point(int x, int y){
    super(x,y);
  }

}


/** An interval represent an integer interval, from one integer low to another integer high.
    The integer low must be smaller than high */
class Interval implements Cloneable{
  private int from;
  private int to; 

  // invariant: non-empty intervals: from < to
  // invariant: empty intervals: from >= to

  /** Return the lower limit of this interval */
  public int from(){return(from);}

  /** Return the upper limit of this interval*/
  public int to(){return(to);}

  private static Interval emptyInterval = new Interval(1,0);

  /** Construct an interval from its two limits. The lower is the first parameter*/  
  public Interval(int from, int to){
    this.from = from; this.to = to;
  }

  /** Return whether the interval is empty: from() is larger than to(). */
  public boolean empty(){
    return (from >= to);
  }

  /** Return the the overlapping interval of this interval and the parameter interval */
  public Interval overlapInterval (Interval other){
   try{
       if (this.empty() || other.empty() )
          return (Interval)emptyInterval.clone();
       else if (this.from > other.to || this.to < other.from)
          return (Interval)emptyInterval.clone();
       else if (this.from < other.from && other.to < this.to)
          return (Interval)other.clone();
       else if (other.from <= this.from && this.to <= other.to)
          return (Interval)this.clone();
       else if (this.from <= other.from && other.from <= this.to)
          return (new Interval(other.from, this.to));
       else if (other.from <= this.from && this.from <= other.to)
          return (new Interval(this.from, other.to));
       else return null;
      }
   catch (CloneNotSupportedException e){
       return null;
   }
 
  }

  /** Return a string representation of this interval */
  public String toString(){
    if (this.empty() )
     return ("Empty interval");
    else return ("Interval: [" + this.from + ", " + this.to + "]");
  }

}


class Rectangle {
  private Point upperLeft, lowerRight;
    // Invariant: upperLeft.x < lowerRight.x
    //            upperLeft.y > lowerRight.y


  /** Construct a rectangle from an upper left point and a lower right point*/
  public Rectangle(Point upperLeft, Point lowerRight){
    this.upperLeft = (Point)upperLeft.clone();
    this.lowerRight = (Point)lowerRight.clone();
  }

  /** Construct a rectangle from an upper left point an a width and a height*/
  public Rectangle(Point upperLeft, int width, int height){
    this.upperLeft = (Point)upperLeft.clone();
    this.lowerRight = new Point(upperLeft.x + width, upperLeft.y - height);
  }

  /** Construct a rectangle from the coordinates of the upper left and the lower right points*/
  public Rectangle(int ulx, int uly, int lrx, int lry){
    this.upperLeft = new Point(ulx,uly);
    this.lowerRight = new Point(lrx, lry);
  }

  /** Return the width of this rectangle */
  public int width(){
    return(java.lang.Math.abs(upperLeft.x - lowerRight.x));
  }


  /** Return the height of this rectangle */
  public int height(){
    return(java.lang.Math.abs(upperLeft.y - lowerRight.y));
  }

  private Point upperLeft(){
    return new Point(upperLeft.x, upperLeft.y);
  }

  private Point lowerLeft(){
    return new Point(upperLeft.x, upperLeft.y - height());
  }

  private Point lowerRight(){
    return new Point(lowerRight.x, lowerRight.y);
  }

  private Point upperRight(){
    return new Point(lowerRight.x, lowerRight.y + height());
  }

  /* Return the four corner points of this rectangle */
  public Point[] corners(){
    Point[] corners = new Point[4];
    corners[0] = upperLeft();
    corners[1] = lowerLeft();
    corners[2] = lowerRight();
    corners[3] = upperRight();
    return(corners);
  }

  /** Move the rectangle relatively with the two parameters, deltaX and deltaY */
  public void moveRelative (int deltaX, int deltaY) {
    upperLeft.move(upperLeft.x + deltaX,upperLeft.y + deltaY);
    lowerRight.move(lowerRight.x + deltaX,lowerRight.y + deltaY);
  }

  /** Return the rectangle formed by this rectangle and its overlap with the parameter rectangle */
  public Rectangle overlappingRectangle(Rectangle other){
    Interval thisxInterval = new Interval(this.upperLeft().x, this.upperRight().x);
    Interval thisyInterval = new Interval(this.lowerLeft().y, this.upperLeft().y);

    Interval otherxInterval = new Interval(other.upperLeft().x, other.upperRight().x);
    Interval otheryInterval = new Interval(other.lowerLeft().y, other.upperLeft().y);

    Interval xOverlap = thisxInterval.overlapInterval(otherxInterval);
    Interval yOverlap = thisyInterval.overlapInterval(otheryInterval);
    
    if (xOverlap.empty() || yOverlap.empty())
      return (new Rectangle(new Point(0,0), 0, 0)); // empty rectangle returned
    else return (new Rectangle(xOverlap.from(), yOverlap.to(), xOverlap.to(), yOverlap.from()));

  }

  /** Return whether this rectangle is empty */
  public boolean empty(){
    return (width() <= 0 || height() <= 0);
  }

  /** Return a string representation of this rectangle */
  public String toString (){
    return("Rectangle: upper left: " + upperLeft() + "lower right: " + lowerRight());
  }

}


public class RectangleProg{

  public static void main(String[] args){
 
    Rectangle r1 = new Rectangle(5, 10, 7, 6);
    Rectangle r2 = new Rectangle(6, 10, 7, 7);
    Rectangle r3 = new Rectangle(new Point(0,0), 0, 0);

    System.out.println("r1: " + r1);
    System.out.println("r2: " + r2);
    System.out.println("r3: " + r3);
  
    System.out.println("Width of r1: " + r1.width() + "  Height of r1: " + r1.height());
    System.out.println("Width of r2: " + r2.width() + "  Height of r2: " + r2.height());
  
//  r2.moveRelative(3,0);

    System.out.println("r1: " + r1);
    System.out.println("r2: " + r2);

    System.out.println("Overlap: " + r1.overlappingRectangle(r2));

  }

}  

  

Her er endvidere et link til det rene Java program


4.2   Klasserne Spillekort og KortSpil  

I forrige lektion var der en opgave om Spillekort, hvor klassen Spillekort repræsenterer et enkelt kort i et kortspil.

Da nogen måske ikke nåede at programmere Spillekort klassen sidste gang, er det en mulighed at løse opgaven ved øvelserne idag.

I min løsning har jeg implementeret en klasse KortSpil, som repræsenterer de 52 kort i spil kort (uden jokere). Vi har ovenfor anbefalet at indlejere Spillekort statisk i KortSpil. Afprøv denne løsning konkret, og demonstrer ved et eksempel, at der statig er adgang til SpilleKort (uden at gå igennem et KortSpil objekt) selv om den er indlejret i KortSpil.

Solution
Først klassen Spillekort fra den orindelige opgave:

public class Spillekort{

  // Farve konstanter:
  public final static int RUDER = 0;
  public final static int HJERTER = 1;  
  public final static int KLØR = 2;  
  public final static int SPAR = 3;  

  // Værdi konstanter: 
  public final static int KNÆGT = 11;  
  public final static int DRONNING = 12;  
  public final static int KONGE = 13;
  public final static int ES = 1;  

  private int farve;
  private int værdi;

  Spillekort(int farve, int værdi){
    this.farve = farve;
    this.værdi = værdi;
  }

  private String værdiPræsentation(){
    if (this.værdi == ES)            return ("Es");
    else if (this.værdi == KNÆGT)    return ("Knægt");
    else if (this.værdi == DRONNING) return ("Dronning");
    else if (this.værdi == KONGE)    return ("Konge");
    else return (Integer.toString(værdi));
  }

  private String farvePræsentation(){
    switch (this.farve) {
      case RUDER:    return("Ruder"); 
      case HJERTER:  return ("Hjerter");
      case KLØR:     return("Klør");
      case SPAR:     return("Spar");
      default: return("???");
    }
  }

  public String toString(){
    return(farvePræsentation() + " " + værdiPræsentation());
  }

  public boolean ligMed(Spillekort andetKort){
    return(this.farve == andetKort.farve && this.værdi == andetKort.værdi);
  }

  public boolean mindreEnd(Spillekort andetKort){
    int thisReelVærdi  = (this.værdi == 1) ? KONGE+1 : this.værdi;
    int andetKortReelVærdi = (andetKort.værdi == 1) ? KONGE+1 : andetKort.værdi;
    return(thisReelVærdi < andetKortReelVærdi);
  }

  public boolean størreEnd(Spillekort andetKort){
    return !(this.ligMed(andetKort) || this.mindreEnd(andetKort));
  }
}
 

Jeg laver en klasse KortSpil, som repræsenterer et helt spil kort:

public class KortSpil{
  
  public Spillekort[] bunke = new Spillekort[52];

  /* Lav et spil kort med 52 kort. */
  public KortSpil(){
    for(int f = Spillekort.RUDER; f <= Spillekort.SPAR; f++)
     for (int v = 1; v <= Spillekort.KONGE; v++){
       Spillekort kort = new Spillekort(f,v);
       bunke[f*13+(v-1)] = kort;
     }
  }

  /* Et hovedprogram som instantierer denne klasse og udskriver KortSpil objektets kort */
  public static void main(String[] args){
    KortSpil spil = new KortSpil();
    for (int i = 0; i < 52; i++)
      System.out.print(spil.bunke[i] + " ");
  }
}

Og nu til løsningen som har at gøre med indlejrede klasser.

Vi vælger en løsning hvor Spillekort er en indre statisk klasse i KortSpil. Dette understreger den samhørighed der er mellem kort begrebet og et helt spil kort, samtidig med at vi accepterer at enkeltkort kan eksisterer uafhængigt af helhedsobjektet.

Her er min løsning:

public class KortSpil{

  static public class Spillekort{
  
    // Farve konstanter:
    public final static int RUDER = 0;
    public final static int HJERTER = 1;  
    public final static int KLØR = 2;  
    public final static int SPAR = 3;  
  
    // Værdi konstanter: 
    public final static int KNÆGT = 11;  
    public final static int DRONNING = 12;  
    public final static int KONGE = 13;
    public final static int ES = 1;  
  
    private int farve;
    private int værdi;
  
    Spillekort(int farve, int værdi){
      this.farve = farve;
      this.værdi = værdi;
    }
  
    private String værdiPræsentation(){
      if (this.værdi == ES)            return ("Es");
      else if (this.værdi == KNÆGT)    return ("Knægt");
      else if (this.værdi == DRONNING) return ("Dronning");
      else if (this.værdi == KONGE)    return ("Konge");
      else return (Integer.toString(værdi));
    }
  
    private String farvePræsentation(){
      switch (this.farve) {
        case RUDER:    return("Ruder"); 
        case HJERTER:  return ("Hjerter");
        case KLØR:     return("Klør");
        case SPAR:     return("Spar");
        default: return("???");
      }
    }
  
    public String toString(){
      return(farvePræsentation() + " " + værdiPræsentation());
    }
  
    public boolean ligMed(Spillekort andetKort){
      return(this.farve == andetKort.farve && this.værdi == andetKort.værdi);
    }
  
    public boolean mindreEnd(Spillekort andetKort){
      int thisReelVærdi  = (this.værdi == 1) ? KONGE+1 : this.værdi;
      int andetKortReelVærdi = (andetKort.værdi == 1) ? KONGE+1 : andetKort.værdi;
      return(thisReelVærdi < andetKortReelVærdi);
    }
  
    public boolean størreEnd(Spillekort andetKort){
      return !(this.ligMed(andetKort) || this.mindreEnd(andetKort));
    }
  }
 
  
  public Spillekort[] bunke = new Spillekort[52];

  /* Lav et spil kort med 52 kort. */
  public KortSpil(){
    for(int f = Spillekort.RUDER; f <= Spillekort.SPAR; f++)
     for (int v = 1; v <= Spillekort.KONGE; v++){
       Spillekort kort = new Spillekort(f,v);
       bunke[f*13+(v-1)] = kort;
     }
  }

  /* Et hovedprogram som instantierer denne klasse og udskriver KortSpil objektets kort */
  public static void main(String[] args){
    KortSpil spil = new KortSpil();
    for (int i = 0; i < 52; i++)
      System.out.print(spil.bunke[i] + " ");
  }
}

Som man ser har vi blot indlejret klasserne, som var resultatet af opgaven fra forrige lektion. Intet mere - intet mindre.

Her er et meget simpelt eksempel på en applikation, som laver et SpilleKort:

// En simpel klasse som illustrerer at SpilleKort kan anvendes direkte og uden problemer
// selv om klassen er indlejret i klassen KortSpil.

class Application {

  static KortSpil.Spillekort k = new KortSpil.Spillekort(KortSpil.Spillekort.RUDER,3);

  public static void main (String[] args) {
    System.out.println(k);
  }
}

Man ser at det er lidt omstændeligt at referere til egenskaber i indre klasser. Derfor kan man med fordel bruge en import clause til at 'åbne op' for den indre klasse:

// En simpel klasse som illustrerer at SpilleKort kan anvendes direkte og uden problemer
// selv om klassen er indlejret i klassen KortSpil

import KortSpil.Spillekort;

class Application1 {

  static Spillekort k = new Spillekort(Spillekort.RUDER,3);

  public static void main (String[] args) {
    System.out.println(k);
  }
}


Generated: Monday March 31, 2008, 12:08:22
on the system cs-unix