Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Expand
titleDel 3 - Familierelasjoner og Relation-implementasjoner (25%)

Denne deloppgaven handler om familierelasjoner, som forelder/far/mor, søsken/søster/bror, onkel/tante, søskenbarn/kusine/fetter, niese/nevø osv.

Sister-klassen skal implementere Relation-grensesnittet (vedlegg 1), slik at metoden getRelativesOf returnerer alle søstrene til Person-argumentet. new Sister().getRelativesOf(person) skal altså returnere alle søstrene til person. Tilsvarende skal klassen Parent implementere Relation slik at metoden getRelativesOf returnerer alle foreldrene, altså mor og/eller far, til Person-argumentet. Merk at Sister- og Parent-klassene ikke står i noe vedlegg.

Oppgave a)

Implementer Sister- og Parent-klassene. 

Expand
titleLF

Parent-klassen er enklest. En må få med både mor og far, og unngå å legge til null-verdier. I Sister-klassen er det laget en hjelpemetode kalt addChildren, som legger alle jente-barn av en gitt Person til en liste og unngår duplikater. Denne kalles med både mor og far til personen en ønsker å finne søstrene til. Merk at dette også vil legge til halvsøstre. Til slutt fjernes personen selv, for hun er jo ikke sin egen søster (subtilt poeng, jeg vet)!

Code Block
public class Sister implements Relation {


   private void addChildren(Person person, Collection<Person> result) {
      if (person != null) {
         for (Person child : person) {
            if (child.getGender() == Gender.FEMALE && (! result.contains(child))) {
                result.add(child);
            }
         }
      }
   }

   @Override
   public Collection<Person> getRelativesOf(Person person) {
          Collection<Person> result = new ArrayList<>();
          addChildren(person.getFather(), result);
          addChildren(person.getMother(), result);
          result.remove(person);
          return result;
    }
}

public class Parent implements Relation {


   @Override
   public Collection<Person> getRelativesOf(Person person) {
      Collection<Person> result = new ArrayList<>();
      if (person.getMother() != null) {
         result.add(person.getMother());
      }
      if (person.getFather() != null) {
         result.add(person.getFather());
      }
      return result;
   }
}
Oppgave b)

Tante-relasjonen kan defineres som søster av forelder, altså en kombinasjonen av Sister- og Parent-logikken. For å slippe å måtte lage nye klasser for slike sammensatte relasjoner, så defineres Relation2-klassen. Den inneholder to Relation-objekter rel1 og rel2 og implementerer Relation slik at metoden getRelativesOf returnerer alle som er rel2-relatert til de som er rel1-relatert til Person-argumentet. Hvis Relation2-klassen instansieres med new Relation2(new Parent(), new Sister()), så vil den i praksis implementere tante-relasjonen ved først å finne foreldrene vha. Parent-objektet og så finne foreldrenes søstre vha. Sister-objektet.

Oppgave b)

Implementer Relation2-klassen. Hva slags standardteknikk er det som brukes her og hva karakteriserer den?

Expand
titleLF

Poenget her er å gjøre som beskrevet i oppgaven, å bruke rel2 på resultatet av å bruke rel1. Her sjekkes det ikke for duplikater (som det sjelden er behov for med relasjoner av denne typen). Dette er delegeringsteknikken i praksis, som kjennetegnes ved at et delegerende objekt, som skal utføre en oppgave, ber en eller flere delegater om å utføre (omtrent) samme oppgave, for så å kombinere resultatene. Her delegeres det til rel1 og rel2, som implementerer samme grensesnitt som Relation2. Det er også en form for komposisjon, men det er ikke pensum.

Code Block
public class Relation2 implements Relation {


   public Relation2(Relation rel1, Relation rel2) {
      this.rel1 = rel1;
      this.rel2 = rel2;
   }

   private final Relation rel1, rel2;

   @Override
   public Collection<Person> getRelativesOf(Person person) {
      Collection<Person> result1 = rel1.getRelativesOf(person);
      Collection<Person> result2 = new ArrayList<>();
      for (Person person1 : result1) {
         result2.addAll(rel2.getRelativesOf(person1));
      }
      return result2;
   }
}
Oppgave c)

Forklar hvordan Relation2-klassen kan brukes til å rigge opp et objekt som implementerer oldeforelder-relasjonen, altså tre foreldre-nivåer unna.

Expand
titleLF

Oldeforelder-relasjonen kan defineres som forelder av forelder av forelder. Dette kan realiseres vha. to (nivåer av) Relation2-objekter (altså uten å skrive en ny klasse!):

Code Block
Relation parent = new Parent();
Relation grandParent = new Relation2(parent, parent);
Relation grandGrandParent = new Relation2(grandParent, parent);
Expand
titleDel 4 - Arv og funksjonelle grensesnitt (15%)
Oppgave a)

Hvis en lager Relation-implementasjoner for søsken, søster og bror, så vil en finne at de er nesten like. En søster er jo en kvinnelig søsken, mens en bror er en mannlig søsken. Forklar med tekst og kode hvordan en kan lage en Sibling-klasse som implementerer søsken-relasjonen, med subklassene Sister og Brother for hhv. søster- og bror-relasjonen. 

Expand
titleLF
 
Oppgave c)

 

Expand
titleLF
 
Oppgave d)

 

Expand
titleLF
 
Oppgave e)

 

Expand
titleLF
 
Oppgave f)

 

Expand
titleLF
 
Expand
titleDel 4 - <tema> (xx%)

En kan lage Sibling-klassen med utgangspunkt i Sister-klassen, og legge til et gender-felt som brukes for å plukke ut og legge til barn med den gitte kjønnet (eller begge, hvis gender er null).Her gjøres det i addChildren-metoden, som må endres. Sister- og Brother-klassene sørger for å initialisere gender-feltet med riktig verdi, evt. vha. en Sister-konstruktør.

En annen variant er å la Sister og Brother filtrere superklassen sitt resultat på kjønn, men dette er ikke like god utnyttelse av arv (kunne i prinsippet brukt delegering). 

Oppgave b)

Hva er en abstrakt klasse? Burde noen av Sibling-, Sister- eller Brother-klassene være abstrakt? Begrunn svaret!

Expand
titleLF
En abstrakt klasse er en klasse som ikke kan instansieres, enten fordi den er ufullstendig ved at den deklarerer én eller flere abstrakte (tomme) metoder, eller fordi det ikke gir mening. Ingen av de tre klassene bør være abstrakte, siden alle er fullstendige og implementerer en nyttig relasjon.
Oppgave c)

Funksjonelle grensesnitt er grensesnitt med noen spesifikke egenskaper, og Relation er et slikt grensesnitt. Forklar hvorfor!

Expand
titleLF
Funksjonelle grensesnitt har bare én metode (krav 1), og den metoden er funksjonell fordi den for samme input(-parametre) alltid gir samme output(-verdi). En annen måte å si det siste på er at den ikke har intern tilstand som påvirker oppførselen og som kan endres. Det er også vanlig å tenke på grensesnitt-metoden som klassens hovedfunksjon.
Oppgave d)

Fullfør deklarasjon under slik at daughter-variablen implementerer datter-relasjonen. daughter.getRelativesOf(person) skal altså returnere døtrene til person.

Relation daughter = ... bruk Java 8-syntaks her ...

Expand
titleLF

Her brukes uttrykksvarianten av funksjonssyntaksen, siden resultatet beregnes enkelt ved å bruke getChildren-metoden. Først har en argumentlista, så en ”pil” og så uttrykket som beregner resultatet. Dette omformes til en implementasjon av Relation med uttrykket som innholdet i getRelationOf-metoden.

Code Block
Relation daughter = (person) -> person.getChildren(Gender.FEMALE);
Oppgave a)

 

Expand
titleLF

 

Oppgave b)

 

Expand
titleLF
 
Oppgave c)

 

Expand
titleLF
 
Oppgave d)

 

Expand
titleLF
 
Oppgave e)

 

Expand
titleLF
 
Oppgave f)

 

Expand
titleLF
 
Expand
titleAppendix 1
Code Block
/**
 * This class represents the gender of a Person.
 * It cannot be instantiated outside this class.
 * It provides all legal Gender values as static variables.
 */
public class Gender {

   ... String label;

   ... Gender(String label) {
      this.label = label;
   }

   @Override
   public String toString() {
      return label;
   }

   public static Gender
      MALE = new Gender("male"),
      FEMALE = new Gender("female");

   /**
    * Returns a pre-existing Gender instance for the provided label, or
    * null of there is no such instance.
    * @param label
    * @return a pre-existing Gender instance
    */
   ... Gender valueOf(String label) {
         ...
   }
}

public class Person implements Iterable<Person> {

   ... fields for name, gender, mother and father ...

   ... constructor ...

   ... methods for name, gender, mother and father ...

   ... field(s) for children ...

   /**
    * @return the number of children of this Person
    */
   public int getChildCount() {
      ...
   }

   /**
    * @param child
    * @return if this Person has the provided Person as a child
    */
   public boolean hasChild(Person child) {
      ...
   }

   /**
    * Returns all children of this Person with the provided Gender.
    * If gender is null, all children are returned.
    * Can be used to get all daughters or sons of a person.
    * @param gender
    */
   public Collection<Person> getChildren(Gender gender) {
      ...
   }

   /**
    * Adds the provided Person as a child of this Person.
    * Also sets the child's father or mother to this Person,
    * depending on this Person's gender.
    * To ensure consistency, if the provided Person already
    * has a parent of that gender,
    * it is removed as a child of that parent.
    * @param child
    */
   public void addChild(Person child) {
      ...
   }
}

public class PersonTest extends TestCase {

   public void testAddChild() {
      Gender female = Gender.valueOf("female"), male = Gender.valueOf("male");
      Person mother = new Person("Chris"); mother.setGender(female);
      Person father1 = new Person("Pat");  father1.setGender(male);
      Person father2 = new Person("Alex"); father2.setGender(male);
      Person child = new Person("Jean");
      mother.addChild(child);
      assertEquals(1, mother.getChildCount());
      assertTrue(mother.hasChild(child));
      assertEquals(mother, child.getMother());
      mother.addChild(child);
      assertEquals(1, mother.getChildCount());

      father1.addChild(child);
      assertTrue(father1.hasChild(child));
      assertEquals(father1, child.getFather());

      father2.addChild(child);
      assertFalse(father1.hasChild(child));
      assertTrue(father2.hasChild(child));
      assertEquals(father2, child.getFather());

      father2.setGender(female);
      father2.addChild(child);
      // ???
      child.addChild(father2);
      // ???
   }
}

public class Family {
   /**
    * Adds a Person as a new family member
    * @param person the Person to add
    */
   public void addMember(Person person) {
      ...
   }

   /**
    * Finds a member with the given name
    * @param name
    * @return the Person in this Family with the provided name
    */

   public Person findMember(String name) {
      ...
   }

   //

   /**
    * Writes the contents of this Family to the OutputStream,
    * so it can be reconstructed using load.
    * @param out
    */
   public void save(OutputStream out) ... {
      ...
   }

   /**
    * Helper method that splits a line into a list of tokens,
    * either words or quoted names (quotes are removed).
    * @param line - the string to tokenize
    */
   ... List<String> tokenize(String line) {
      ... no need to implement this method ...
   }

   /**
    * Loads contents from the provided InputStream into this Family.
    * @param in
    */
   public void load(InputStream in) ... {
      ...
   }
}

public interface Relation {
   /*
    * Returns the Collection of Persons related to the provided Person
    * according to this Relation.
    * E.g. if this Relation corresponds to the concept of niece,
    * it should return all Persons that are nieces of person.
    */
   Collection<Person> getRelativesOf(Person person);
}

public class Relation2 implements Relation {

   public Relation2(Relation rel1, Relation rel2) {
      ...
   }

   @Override
   public Collection<Person> getRelativesOf(Person person) {
      ...
   }
}

...