© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Polymorfi
Vejledende løsninger

 

 

1 Først laver vi klassen Forsendelse:
package Post;

public abstract class Forsendelse {
  
  protected double vægt; // i gram
  
  public Forsendelse( double v ) {
    vægt = v;
  }
  
  public abstract double porto();
}
Vi vælger her at lade datakernen være en angivelse af vægten i gram. Det er ikke nødvendigt at anvende en double, en int ville have været tilstrækkelig, men det er alligevel valgt, da en vægt traditionelt angives med et komma-tal.
Metoden porto, er den metode der gør klassen abstrakt.
Dernæst laver vi klassen Pakke:
package Post;

public class Pakke extends Forsendelse {
  
  public Pakke( double v ) {
    super( v );
  }
  
  public double porto() {
    if ( vægt <= 1000 )
      return 30.25;
    else if ( vægt <= 5000 )
      return 32.25;
    else if ( vægt <= 10000 )
      return 41.00;
    else if ( vægt <= 15000 )
      return 61.00;
    else if ( vægt <= 20000 )
      return 67.00;
    else
      return -1;
  }
  
  public String toString() {
    return "[Pakke: " + vægt + " gram]";
  }
}
Pakke er den første konkrete klasse, og vi implementerer derfor porto. Dette gøres vha. en række if-sætninger.
Dernæst laver vi klassen Brev:
package Post;

public abstract class Brev extends Forsendelse {
  
  public Brev( double v ) {
    super( v );
  }
}
Denne klasse er abstrakt idet vi ikke implmenterer porto. Vi laver en set-konstruktor afht. de to subklasser.
Dernæst laver vi klassen ABrev:
package Post;

public class ABrev extends Brev {

  public ABrev( double v ) {
    super( v );
  }
  
  public double porto() {
    if ( vægt <= 20 )
      return 4.00;
    else if ( vægt <= 100 )
      return 5.25;
    else if ( vægt <= 250 )
      return 9.25;
    else if ( vægt <= 500 )
      return 16.00;
    else if ( vægt <= 1000 )
      return 20.00;
    else if ( vægt <= 2000 )
      return 28.00;
    else
      return -1;
  }
  
  public String toString() {
    return "[ABrev: " + vægt + " gram]";
  }
}
Her implementerer vi porto på samme måde som det er gjort i klassen Pakke.
Dernæst laver vi klassen BBrev:
package Post;

public class BBrev extends Brev {
  
  public BBrev( double v ) {
    super( v );
  }
  
  public double porto() {
    if ( vægt <= 20 )
      return 3.75;
    else if ( vægt <= 100 )
      return 5.00;
    else if ( vægt <= 250 )
      return 8.75;
    else if ( vægt <= 500 )
      return 15.00;
    else if ( vægt <= 1000 )
      return 19.00;
    else if ( vægt <= 2000 )
      return 27.00;
    else
      return -1;
  }
  
  public String toString() {
    return "[BBrev: " + vægt + " gram]";
  }
}
porto implementeres analogt til de andre implementationer af denne metode.

 

Endelig har vi en test-anvendelse.

import Post.*;

class TestPost {
  
  public static void main( String[] argv ) {
    
    ABrev a = new ABrev( 140 );
    System.out.println( a + " porto=" + a.porto() );
    
    BBrev b = new BBrev( 140 );
    System.out.println( b + " porto=" + b.porto() );
    
    Pakke p = new Pakke( 1400 );
    System.out.println( p + " porto=" + p.porto() );
  }
}

[ABrev: 140.0 gram] porto=9.25
[BBrev: 140.0 gram] porto=8.75
[Pakke: 1400.0 gram] porto=32.25

Kildetekster:

Bemærk at de fem første filer skal placeres i et subdirectory kaldet Post.

Forsendelse.java
Pakke.java
Brev.java
ABrev.java
BBrev.java
TestPost.java

2 Først laver vi den abstrakte superklasse:
package Bolig;

public abstract class Bolig {
  
  protected double m2;
  
  public Bolig( double m2 ) {
    this.m2 = m2;
  }
  
  public Bolig( Bolig bolig ) {
    m2 = bolig.m2;
  }
  
  public abstract double årligHusleje();
  
  public String toString() {
    return "[Bolig: m2=" + m2 + "]";
  }
}
Her har vi erklæret metoden årlighusleje abstrakt, idet det er op til subklasserne at levere implementationen. Metoden er erklæret i Bolig, da vi ønsker kunne kalde den polymorft via en Bolig-reference.
Vi har gjort klassen public, da vi ellers ikke vil kunne anvende den uden for pakken. Filen med denne klasse, og de to næste, skal placeret i et subdirectory, der bærer samme navn som pakken: Bolig.
Bemærk at datakernen er protected, selv om det strengt taget ikke er nødvendigt, da den ikke i denne løsning anvendes i nogen af subklasserne.
Dernæst laver vi klassen Villa, der nedarver fra Bolig:
package Bolig;

public class Villa extends Bolig {
  
  private double pris, grund;
  private boolean byZone;
  
  public Villa( double m2, double p, double g, boolean bz ) {
    super( m2 );
    
    pris = p;
    grund = g;
    byZone = bz;
  }
  
  public Villa( Villa bolig ) {
    super( bolig.m2 );

    pris   = bolig.pris;
    grund  = bolig.grund;
    byZone = bolig.byZone;
  }
  
  public double årligHusleje() {
    double normalLeje = pris * 0.10;
    
    if ( byZone )
      return 1.20 * normalLeje;
    else
      return normalLeje;
  }
  
  public String toString() {
    return "[Villa: pris=" + pris +
             ", grund=" + grund +
             ", byZone=" + byZone +
             " " + super.toString() + "]";
  }
}
Bemærk at toString anvender superklassens toString og derved undgår at tilgå m2 direkte. Det samme gør sig gældende for konstruktorerne, der bruger superklassens konstruktorer.
Datakernen i Villa, og Lejlighed, er naturligvis private, da de ikke her subklasser.
Dernæst kommer den anden subklasse, Lejlighed, som ikke adskiller sig fra Villa i nævneværdig grad.
package Bolig;

public class Lejlighed extends Bolig {
  
  private double månedligHusleje;
  
  public Lejlighed( double m2, double mLeje ) {
    super( m2 );
    
    månedligHusleje = mLeje;
  }
  
  public Lejlighed( Lejlighed bolig ) {
    super( bolig.m2 );
    
    månedligHusleje = bolig.månedligHusleje;
  }
  
  public double årligHusleje() {
    return 12.0 * månedligHusleje;
  }
  
  public String toString() {
    return "[Lejlighed: månedligHusleje=" + månedligHusleje +
             " " + super.toString() + "]";
  }
}
Endelig har vi en testanvendelse, der illustrerer polymorfien ved at bruge abstrakte referencer. Bemærk, at det derfor er nødvendigt at caste referencerne i forbindelse med copy-konstruktorerne.
import Bolig.*;

class TestBolig {
  
  public static void main( String[] argv ) {
    Bolig b1 = new Lejlighed( 70, 3000 );
    System.out.println( b1 + " årlig husleje: " + b1.årligHusleje() );
    
    Bolig b2 = new Villa( 110, 650000, 800, false );
    System.out.println( b2 + " årlig husleje: " + b2.årligHusleje() );
    
    Bolig b3 = new Villa( 140, 850000, 400, true );
    System.out.println( b3 + " årlig husleje: " + b3.årligHusleje() );
    
    Bolig b4 = new Lejlighed( (Lejlighed) b1 );
    System.out.println( b4 + " årlig husleje: " + b4.årligHusleje() );
    
    Bolig b5 = new Villa( (Villa) b3 );
    System.out.println( b5 + " årlig husleje: " + b5.årligHusleje() );
  }
}

[Lejlighed: månedligHusleje=3000.0 [Bolig: m2=70.0]] årlig husleje: 36000.0
[Villa: pris=650000.0, grund=800.0, byZone=false [Bolig: m2=110.0]] årlig husleje: 65000.0
[Villa: pris=850000.0, grund=400.0, byZone=true [Bolig: m2=140.0]] årlig husleje: 102000.0
[Lejlighed: månedligHusleje=3000.0 [Bolig: m2=70.0]] årlig husleje: 36000.0
[Villa: pris=850000.0, grund=400.0, byZone=true [Bolig: m2=140.0]] årlig husleje: 102000.0

Kildetekster:

Bemærk at de tre første filer skal placeres i et subdirectory kaldet Bolig.

Bolig.java
Villa.java
Lejlighed.java
TestBolig.java

 

3 Først laver vi den abstrakte klasse Bil:
package Bil;

public abstract class Bil {
  
  private double vægt;
  
  public Bil( double v ) {
    vægt = v;
  }
  
  public Bil( Bil bil ) {
    this( bil.vægt );
  }
  
  public abstract double afgift();
  
  protected double vægtAfgift( double ører )  {
    return vægt/100.0*ører;
  }
  
  public String toString() {
    return "[Bil: vægt=" + vægt + "]";
  }
}
Vi har her gjort metoden afgift abstrakt. Selv om vægt indgår i beregningen af afgift for både LastBil og PersonBil, sker det på en måde, der ikke giver anledning til en fælles implementation i afgift. I stedet er det nyttigt med en service-metode til sub-klasserne, der beregner den del af afgiften som beror på Bil'ens vægt. Metoden vægtAfgift tager afgiften i ører pr. kg. Den er protected for at subklasserne kan anvende den, og samtidig giver den, den eneste adgang til vægt som subklasserne behøver - derved kan vi styrke indkapslingen ved at gøre vægt private.
 
Lad os se den første af subklasserne LastBil:
package Bil;

public class LastBil extends Bil {
  
  private double last;
  
  public LastBil( double v, double lt ) {
    super( v );
    last = lt;
  }
  
  public LastBil( LastBil bil ) {
    super( bil );
    last = bil.last;
  }
  
  public double afgift() {
    return vægtAfgift( 35.0 ) + last/100.0*20.0;
  }
  
  public String toString() {
    return "[LastBil: last=" + last + " " + super.toString() + "]";
  }
}
Bemærk, at samtlige metoder i denne klasse anvender metoder fra super-klassen. Konstruktorerne anvender super-klassens konstruktorer til at initialisere vægt, og metoderne afgift og toString anvender de get-agtige metoder i superklassen, ligeledes vedrørende vægt.
Det er også værd at bemærk, at copy-konstruktoren kalder Bil's copy-konstruktor med en LastBil som aktuel parameter. Da den formelle parameter af typen Bil også kan referere til en LastBil, er dette ikke noget problem.
 
Dernæst følger den anden subklasse PersonBil:
package Bil;

public class PersonBil extends Bil {
  
  private int passagerer;
  
  public PersonBil( double v, int p ) {
    super( v );
    passagerer = p;
  }
  
  public PersonBil( PersonBil bil ) {
    super( bil );
    passagerer = bil.passagerer;
  }
  
  public double afgift() {
    return vægtAfgift( 50.0 ) + passagerer*800.0;
  }
  
  public String toString() {
    return "[PersonBil: passagerer=" + passagerer + " " + super.toString() + "]";
  }
}
Man bemærker at denne subklasser grundlæggende ikke adskiller sig fra LastBil - den er opbygget på samme måde.
 
Endelig har vi en testanvendelse:
import Bil.*;

class TestBil {
  
  public static void main( String[] argv ) {

    Bil last1 = new LastBil( 2000, 400 );
    System.out.println( last1 + " afgift: " + last1.afgift() );
    
    Bil person1 = new PersonBil( 1100, 5 );
    System.out.println( person1 + " afgift: " + person1.afgift() );
    
    Bil last2 = new LastBil( (LastBil) last1 );
    System.out.println( last2 + " afgift: " + last2.afgift() );
    
    Bil person2 = new PersonBil( (PersonBil) person1 );
    System.out.println( person2 + " afgift: " + person2.afgift() );
  }
}

[LastBil: last=400.0 [Bil: vægt=2000.0]] afgift: 780.0
[PersonBil: passagerer=5 [Bil: vægt=1100.0]] afgift: 4550.0
[LastBil: last=400.0 [Bil: vægt=2000.0]] afgift: 780.0
[PersonBil: passagerer=5 [Bil: vægt=1100.0]] afgift: 4550.0

Kildetekster:

Bemærk at de tre første filer skal placeres i et subdirectory kaldet Bil.

Bil.java
LastBil.java
PersonBil.java
TestBil.java

 

4 Først laver vi den abstrakte superklasse Medlem:
package Idraet;

public abstract class Medlem {
  
  protected String navn;
  protected int alder;
  
  public Medlem( String n, int a ) {
    navn = n;
    alder = a;
  }
  
  public abstract int kontingent();
  
  public String toString() {
    return "[navn=" + navn + ", alder=" + alder + "]";
  }
}
Bemærk at metoden kontingent er erklæret abstrakt.
Dernæst laver vi klassen Junior, der implementerer kontingent:
package Idraet;

public class Junior extends Medlem {
  
  private String parent;
  
  public Junior( String n, int a, String p ) {
    super( n, a );
    parent = p;
  }
  
  public int kontingent() {
    if ( alder < 15 )
      return 150;
    else
      return 250;
  }
  
  public String toString() {
    return "[Junior:" + super.toString() + ", forældre: " + parent + "]";
  }
}
Bemærk at anvendelsen af super-kald i realiteten gør det overflødigt at vi har en protected datakerne i Medlem, den kunne godt have været private.
Dernæst følger klassen Senior:
package Idraet;

public class Senior extends Medlem {
  
  public Senior( String n, int a ) {
    super( n, a );
  }
  
  public int kontingent() {
    if ( alder < 40 )
      return 400;
    else if ( alder < 60 )
      return 350;
    else
      return 100;
  }
  
  public String toString() {
    return "[Senior:" + super.toString() + "]";
  }
}
Endelig har vi testanvendelsen, der illustrerer polymorfien, ved at anvende abstrakte referencer til de tre instanser:
import Idraet.*;

class TestIdraet {
  
  public static void main( String[] argv ) {
    
    Medlem m1 = new Senior( "Erling", 75 );
    System.out.println( m1 + ", kontingent: " + m1.kontingent() );
    
    Medlem m2 = new Junior( "Hans", 9 , "Valdemar" );
    System.out.println( m2 + ", kontingent: " + m2.kontingent() );
    
    Medlem m3 = new Junior( "Peter", 16, "Vera" );
    System.out.println( m3 + ", kontingent: " + m3.kontingent() );
  }
}

[Senior:[navn=Erling, alder=75]], kontingent: 100
[Junior:[navn=Hans, alder=9], forældre: Valdemar], kontingent: 150
[Junior:[navn=Peter, alder=16], forældre: Vera], kontingent: 250

Kildetekster:

Bemærk at de tre første filer skal placeres i et subdirectory kaldet Idraet.

Medlem.java
Junior.java
Senior.java
TestIdraet.java

 
5 Først laver vi den abstrakte superklasse Lokale:
package Lokaler;

public abstract class Lokale {
  private double areal;
  
  public Lokale( double areal ) {
    this.areal  = areal;
  }
  
  public Lokale( Lokale lok ) {
    this( lok.areal );
  }
  
  public abstract int kapacitet();
  
  protected int arealKapacitet( double prM2 ) {
    return (int) (areal/prM2);
  }
  
  public String toString() {
    return "[Lokale: areal=" + areal + "]";
  }
}
Bemærk at metoden kapacitet er erklæret abstrakt.
Dernæst laver vi klassen KlasseLokale, der implementerer kapacitet:
package Lokaler;

public class KlasseLokale extends Lokale {
  
  public KlasseLokale( double areal ) {
    super( areal );
  }
  
  public KlasseLokale( KlasseLokale lok ) {
    super( lok );
  }
  
  public int kapacitet() {
    return arealKapacitet( 1.5 );
  }
  
  public String toString() {
    return "[KlasseLokale: " + super.toString() + "]";
  }
}
Bemærk at anvendelsen af arealKapacitet gør det muligt at bibeholde areal som værende private i super-klassen Lokale.
Dernæst følger klassen EDB_Lokale:
package Lokaler;

public class EDB_Lokale extends Lokale {
  private int strøm;
  
  public EDB_Lokale( double areal, int strøm ) {
    super( areal );
    this.strøm = strøm;
  }
  
  public EDB_Lokale( EDB_Lokale lok ) {
    super( lok );
    this.strøm = lok.strøm;
  }
  
  public int kapacitet() {
    int kap = arealKapacitet( 2.5 );
    
    if ( kap > strøm/3 )
      return strøm/3;
    else
      return kap;
  }
  
  public String toString() {
    return "[EDB_Lokale: " + super.toString() + ", strøm=" + strøm + "]";
  }
}
Endelig har vi testanvendelsen, der illustrerer polymorfien, ved at anvende abstrakte referencer til de fem instanser. Bemærk at anvendelse af et array gør det bekvemt at kalde de polymorfe metoder i en løkke:
import Lokaler.*;

public class Test {

  public static void main( String[] argv ) {
    Lokale[] lokaler = new Lokale[5];
    
    lokaler[0] = new KlasseLokale( 40 );
    lokaler[1] = new KlasseLokale( 30 );
    lokaler[2] = new KlasseLokale( (KlasseLokale) lokaler[0] );
    
    lokaler[3] = new EDB_Lokale( 20, 25 );
    lokaler[4] = new EDB_Lokale( 60, 70 );
    
    for ( int i=0; i<lokaler.length; i++ )
      System.out.println( lokaler[i] + ": " + lokaler[i].kapacitet() );
  }
}

[KlasseLokale: [Lokale: areal=40.0]]: 26
[KlasseLokale: [Lokale: areal=30.0]]: 20
[KlasseLokale: [Lokale: areal=40.0]]: 26
[EDB_Lokale: [Lokale: areal=20.0], strøm=25]: 8
[EDB_Lokale: [Lokale: areal=60.0], strøm=70]: 23

Kildetekster:

Bemærk at de tre første filer skal placeres i et subdirectory kaldet Lokaler.

Lokale.java
KlasseLokale.java
EDB_Lokale.java
Test.java

 
6 Først laver vi den abstrakte superklasse Tog:
package Tog;

public abstract class Tog {
  private int standardPladser;
  
  public Tog( int standardPladser ) {
    this.standardPladser = standardPladser;
  }
  
  public Tog( Tog tog ) {
    this( tog.standardPladser );
  }
  
  public abstract int billetIndtægt();
  
  protected int standardIndtægt() {
    return 20*standardPladser;
  }
  
  public String toString() {
    return "[Tog: standardPladser=" + standardPladser + "]";
  }
}
Bemærk at metoden billetIndtægt er erklæret abstrakt.
Dernæst laver vi klassen ReginalTog, der implementerer billetIndtægt:
package Tog;

public class RegionalTog extends Tog {
  
  public RegionalTog( int standardPladser ) {
    super( standardPladser );
  }
  
  public RegionalTog( RegionalTog tog ) {
    super( tog );
  }
  
  public int billetIndtægt() {
    return standardIndtægt();
  }
  
  public String toString() {
    return "[RegionalTog: " + super.toString() + "]";
  }
}
Bemærk at anvendelsen af standardIndtægt gør det muligt at bibeholde standardPladser som værende private i super-klassen Tog.
Dernæst følger klassen LynTog:
package Tog;

public class LynTog extends Tog {
  private int businessPladser;
  
  public LynTog( int standardPladser, int businessPladser ) {
    super( standardPladser );
    
    this.businessPladser = businessPladser;
  }
  
  public LynTog( LynTog tog ) {
    super( tog );
  
    this.businessPladser = tog.businessPladser;
  }
  
  public int billetIndtægt() {
    return standardIndtægt() + businessPladser * 50;
  }
  
  public String toString() {
    return "[LynTog: " + super.toString() + ", businessPladser=" + businessPladser + "]";
  }
}
Endelig har vi testanvendelsen, der illustrerer polymorfien, ved at anvende abstrakte referencer til de tre instanser. Bemærk at anvendelse af et array gør det bekvemt at kalde de polymorfe metoder i en løkke:
import Tog.*;

public class TestTog {

  public static void main( String[] argv ) {
    Tog[] tog = new Tog[3];
    
    tog[0] = new RegionalTog( 120 );
    tog[1] = new RegionalTog( (RegionalTog) tog[0] );
    
    tog[2] = new LynTog( 80, 30 );
    
    for ( int i=0; i<tog.length; i++ )
      System.out.println( tog[i] + " indtægt: " + tog[i].billetIndtægt() );
  }
}

[RegionalTog: [Tog: standardPladser=120]] indtægt: 2400
[RegionalTog: [Tog: standardPladser=120]] indtægt: 2400
[LynTog: [Tog: standardPladser=80], businessPladser=30] indtægt: 3100

Kildetekster:

Bemærk at de tre første filer skal placeres i et subdirectory kaldet Tog.

Tog.java
RegionalTog.java
LynTog.java
TestTog.java