Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

  • sammenhengen mellom instansiering , typen til objekter og typer og (deklarasjonen av) typen til variabler og tilordninger
  • regler for oppførsel og innkapsling

...

De tre objektene identifisert som book1, nynorsk og flatFirer er instanser av hver sin klasse, henholdsvis Book, Dictionary og CartoonAlbum. Samtidig, og her starter gjerne forvirringen, så sier vi at #nynorsk nynorsk og #flatFirer flatFirer også er instanser av eller er av typen Book, fordi de har alle egenskapene til Book-instanser og derfor kan betraktes som slike. Hvis en f.eks. bruker instanceof-operatoren i Java så vil den virke som vist i tabellen under:

...

Vi ser altså at alle objektene er instanceof Book og at book1 ikke er instanceof de to subklassene. Så hvis Hvis en altså sier at et objekt er en instans av en bestemt type T, så betyr det ikke nødvendigvis at en brukte new T(...) for å lage den, men new <T eller en subklasse av T>(...). Dette er analogt med at en kan si at et objekt er en instans av (grensesnittet) Iterator, selv om en en nødvendigvis ikke har brukt new Iterator(...) for å opprette objektet. Hvis en ønsker å si nettopp det at en brukte new T(...) så kan en kanskje si at objektet ble instansiert med som T.

Sitiuasjonen Situasjonen er nokså tilsvarende med deklarasjoner og tilordninger. Når en deklarerer en variabel (eller felt eller parameter) til å være av en type T, så sier en at denne variablen bare kan tilordnes instanser av T, eller mer presist, at den bare kan tilordnes verdien av uttrykk som har typen T. I koden under vises ulike tilfeller og kommentaren angir om det er lov eller ikke og hvorfor.

Code Block
Book book1 = new Book(); // OK siden new Book() gir et object av typen Book
Book book2 = new Dictionary(); // OK siden new Dictionary() gir et objekt av typen Book
Book book3 = new CartoonAlbum(); // OK siden new CartoonAlbum() gir et objekt av typen Book

Dictionary dict1 = new Book(); // ikke OK siden new Book() ikke gir et object av typen Dictionary
Dictionary dict2 = new Dictionary(); // OK siden new Dictionary() gir et objekt av typen Dictionary
Dictionary dict3 = new CartoonAlbum(); // ikke OK siden new CartoonAlbum() ikke gir et objekt av typen Dictionary

CartoonAlbum cartoon1book1 = new Book()book2; // ikke OK siden new book2 er deklarert som Book() og ikkedette girer etsamme objecttype avsom typenbook1-variablen CartoonAlbum
CartoonAlbumbook1 cartoon2 = new Dictionary()dict1; // ikke OK siden new Dictionary() gir et objekt dict1 er deklarert som Dictionary og dette er en subklasse av typen CartoonAlbum
CartoonAlbum cartoon3 = new CartoonAlbum()til book1-variablen
book1 = cartoon1; // OK siden new CartoonAlbum() gir et objekt cartoon1 er deklarert som CartoonAlbum og dette er en subklasse av typen CartoonAlbum som book1-variablen er deklarert som

book1dict1 = book2; // ikke OK siden book2 er deklarert som Book og dette ikke er samme type som book1-variablen er deklarert som 
book1 = dict1 eller en subtype av Dictionary 
dict1 = dict2; // OK siden dict2 er deklarert som Dictionary og dette er samme type som dict1-variablen
dict1 = cartoon1; // ikke OK siden dict1cartoon1 er deklarert som DictionaryCartoonAlbum og dette ikke er samme eller en subtype av Dictionary

Regelen er altså at en variable (eller felt eller parameter) av type T bare kan tilordnes verdien av et uttrykk, når uttrykket har type T eller en subklasse av type T.

Et vesentlig poeng er skillet mellom typen til uttrykket og typen til verdien av uttrykket, som er illustrert av følgende setninger:

Code Block
Book book = new Dictionary(); // OK, se over
Dictionary dict = book; // ikke OK, selv om book faktisk refererer til en instans av Dictionary, så er det typen somtil book1book-variablen ersom sjekkes deklarert som
book1 = cartoon1og som må være Dictionary eller en Dictionary-subklasse 

Typen til et uttrykk kan lese direkte av (typen til delene av) uttrykket, mens det andre krever at en kjører koden og ser hva slags verdi en faktisk får. Dersom en skal ha garantier om at noe alltid er lov, må en altså se på typen til uttrykket og ikke vente til kjøretid og sjekke typen til verdien av uttrykket. Dette ser en enklest med følgende setning:

Code Block
Dictionary dict = (Math.random() < 0.5 ? new Book() : new Dictionary()); // ikke OK, fordi typen sidentil cartoon1if-uttrykket er deklarert som CartoonAlbum og dette er en subklasse av typen som book1-variablen er deklarert som

 

Book 

Selv om en i (omtrent) annenhvert tilfelle får en (referanse til en) Dictionary som verdi av uttrykket, så kan en bare garantere at resultatet blir en Book og da kan en ikke tillate tilordningen.

Innkapsling

Ved innkapsling så prøver en å sikre korrekt bruk av metoder og konsistent tilstand, bla. ved å sikre at tilstanden initialiseres og at argumenter valideres. Anta at Book er skrevet som vist under, for å kapsle inn tilstanden ordentlig:

Code Block
public class Book {

   private String title;

   public Book(String title) {
      setTitle(title);
   }

   public String getTitle() {
      return title;
   }

   public void setTitle(String title) {
      if (! <sjekk at title bare inneholder bokstaver og tall>) {
          throw new IllegalArgumentException("Book titles can only contain letters and digits");
      }
      this.title = title;
   }
}
Code Block
// Feil!!!

public class Dictionary {

   private int wordCount;

   // ingen initialisering av title
   public Dictionary(int wordCount) {
      this.wordCount = wordCount;
   }

   // egen validering av title
   public void setTitle(String title) {
      if (! <sjekk at title slutter på 'ordbok'>) {
          throw new IllegalArgumentException("Dictionary titles must end with 'ordbok'");
      }
      this.title = title;
   }
}
Code Block
// Riktig!!!

public class Dictionary {

   private int wordCount;

   // title initialiseres med super(...)
   public Dictionary(String title, int wordCount) {
      // kall først superklassens konstruktør
      super(title);
      this.wordCount = wordCount;
   }

   // bruker superklassens setTitle-metode
   public void setTitle(String title) {
      if (! <sjekk at title slutter på 'ordbok'>) {
          throw new IllegalArgumentException("Dictionary titles must end with 'ordbok'");
      }
      super.setTitle(title);
   }
}

Denne koden sikrer to ting: 1) at title-attributtet får en initiell verdi og 2) at title-verdien alltid er en String med kun bokstaver og tall. Dette påvirker ikke bare hva andre klasser kan gjøre med Book-instanser, men også hva Dictionary og CartoonBook kan gjøre med sine instanser, siden de jo også er Book-instanser og derfor må følge reglene for slike. F.eks. kan ikke subklassene utelate initialisering og heller ikke slakke på kravene som stilles til tegnene i en title. Koden i midten høyre prøver begge deler og må derfor avvises. Mer spesifikt vil en få feilmelding om at 1) konstruktøren ikke kaller en konstruktør i superklassen og 2) at title-attributtet ikke er synlig og derfor ikke kan settes i setTitle-metoden. Alternativet er koden til høyre, hvor super-nøkkelordet brukes på to måter: 1) til å kalle superklassens konstruktør (kan bare gjøres i konstruktøren) og 2) til å kalle superklassens setTitle-metode (kan gjøres i alle metoder).

Innkapslingen i Book-klassen er imidlertid fortsatt vanntett, så Dictionary-klassen kan fortsatt ikke omgå valideringen i superklassens setTitle-metode og dermed slakke på kravene. Dette kan løses på to måter, enten ved å åpne opp for direkte tilgang til title-attributtet, men kun for subklasser eller ved å la subklassen redefinere valideringslogikken.

Subklasse-synlighet med protected-modifikatoren

Det finnes en egen synlighetsmodifikator som gir subklasser tilgang til attributter og metoder definert i en superklasse, nemlig protected. Dette kan brukes for å gi Dictionary-subklassen direkte tilgang til title-attributtet og dermed muligheten til å omgå valideringen:

Code Block
public class Book {

   protected String title;

   ...

   public void setTitle(String title) {
      if (! ...) {
          throw new IllegalArgumentException(...);
      }
      this.title = title;
   }
}
Code Block
public class Dictionary {

   ...

   public void setTitle(String title) {
      if (! ...) {
          throw new IllegalArgumentException(...);
      }
      // fordi title-attributtet er deklarert med protected,
      // så har vi nå lov til å endre det direkte
      this.title = title;
   }
}

Bruk og redefinering av metoder

En annen teknikk for å la en subklasse definere alternative regler for oppførsel, er å skille ut logikk i egne metoder som kan redefineres. Anta f.eks. at en forutser behovet for å definere subklasse-spesifikke regler for gyldige title-verdier. Dette kan håndteres som vist under:

Code Block
public class Book {

   private String title;

   ...

   protected boolean isValidTitle(String title) {
      return <true hvis title bare inneholder bokstaver og tall, og false ellers>
   }

   public void setTitle(String title) {
      if (! isValidTitle(title)) {
          throw new IllegalArgumentException(...);
      }
      this.title = title;
   }
}
Code Block
public class Dictionary {

   ...

   @Override
   protected boolean isValidTitle(String title) {
      return <true hvis title slutter på 'ordbok', og false ellers>
   }
}

I Book-koden over har en lagt valideringslogikken i en egen isValidTitle-metode, som er gjort synlig for subklassene med protected-modifikatoren. Dictionary-klassen redefinerer denne metoden med sin egen logikk. Hvis en kaller setTitle på et objekt instansiert som Book, så vil Book-klassen sin isValidTitle-metode bli kalt. Men dersom objektet er en Dictionary-instans, så vil (den redefinerte) metoden i Dictionary-klassen bli brukt i stedet. Dermed kan Dictionary-spesifikke valideringsregler overstyre valideringsreglene i Book, som jo var intensjonen. Dictionary-klassen får altså fortsatt ikke direkte tilgang til title-attributtet, men får lov til å endre/overstyre valideringslogikken.

Ved hjelp av protected-modifikatoren og redefinering av metoder kan altså en superklasse tillate at visse regler endre/overstyres av en subklasse. Dersom subklassen ønsker å bruke reglene til superklassen, så kan en (selektivt) bruke metodekall med super-nøkkelordet. Dictionary-klassen kan f.eks. kalle super.isValidTitle(title) hvis den ønsker å (gjen)bruke superklassens logikk.