Innkapsling og validering - Teorispørsmål

  1. a) Hva er forskjellen påpublic, protected og private? public brukes når andre klasser skal kunne instansiere klassen direkte med **new (…)**</span>. **protected**-konstruktører brukes for å initialisere felt i en superklasse og kalles av subklassens konstruktør med **super(…)**. **private** brukes på hjelpekonstruktører internt i en klasse og kalles med **this(…)**. b) Hva bør i utgangspunktet alle variablene dine være deklarert som? Innkapsling handler om å: 1) hindre at en kan sette objekter til en ulovlig tilstand og 2) skjule representasjonsdetaljer slik at en kan gjøre endringer uten at andre klasser også må endres.
  2. Hvilke to metoder er vanlig å ha for variabler i Java? Generelt er det en fordel å redusere settet med metoder, siden det gir frihet til å endre klasse siden. Når felt initaliseres i en konstruktør, så trengs som oftest ikke set-metoder. Her trenger en heller ikke get-metoder, siden metodene som stiller spørsmål og sjekker svar utgjør et komplett API for klassen. get-metoder gir tilgang til tilstanden uten å ”avsløre” hvilke felt som brukes for å representere tilstanden.
  3. Hva er forskjellen på ugyldig tilstand og ugyldig argument? Hva er formålet med / begrunnelsen for å implementere en eller flere konstruktører for en klasse : En konstruktør har som formål å initialisere et objekt, slik at det fra starten av har en gyldig tilstand.
  4. Hva er meningen med innkapsling og validering? Innkapsling skal sikre et objekt starter og forblir i en gyldig tilstand og at innholdet kan leses og endres uten å avdekke den interne representasjonen og implementasjonen. Mekanismen er bruk av synlighetsmodifikatorer, som reduserer muligheten (andre) klasser har til å referere til felt og metoder.

En kan kategorisere innkapslingsmetoder som enten lese- eller endringsmetoder. Den viktigste oppgaven til endringsmetodene, bortsett fra å utføre selve endringen er å sjekke (validere) om den nye verdien (eller nye verdiene) er lovlige/gyldige, før de evt. endres, f.eks. at et navn kun inneholder bokstaver og mellomrom.

  1. Trenger man å validere argumenter som tas inn i konstruktøren? public final int hours, minutes; final betyr her at feltet ikke kan endres etter at det er satt i konstruktøren. Selv om feltene er public så sikres innkapsling ved at felt-verdiene forblir korrekte, siden kode utenfor klassen ikke kan sette feltene til ugyldige verdier Imidlertid er det ikke i tråd med innkapsling at kode gjøres avhengig av at data er lagret i spesifikke felt. Ved bruk av get-metoder får implementasjonsklassen større frihet til å endre interne detaljer, uten at annen kode blir påvirket. final betyr her at feltet ikke skal kunne endres etter at det er initialisert. Da må en enten initialisere i deklarasjonen eller i en eller flere konstruktører, eller en kombinasjon. Klasser utformet slik at instanser ikke skal kunne endres etter at de er opprettet, generelle fordeler og ulemper med klasser som gir ikke-modifiserbare (immutable) instanser er at klassen blir enklere, og minsker bl.a. behovet for validering. Instanser kan brukes av flere deler av et program, uten risiko for at en del endrer på dem og ødelegger for en annen del. Ulempen er at en må lage nye instanser hvis de må rettes på, istedenfor å endre dem direkte. Den generelle teknikken og navnekonvensjonen(e) for å representere og kapsle inn en enkel verdi, f.eks. tall eller objektreferanse, som skal kunne endres etter at objektet er opprettet: **private T value; public T getValue() { … } **(Dersom X er boolean/Boolean, så brukes gjerne ”is” som prefiks istedenfor ”get”). public void setValue(T value) { … } Med static-modifikatoren vil metodene ikke kunne referere til et bestemt RadioAlphabet-objekt. En kan for så vidt gjøre alfabetet static også, men da vil en bare kunne ha ett globalt alfabet. En metode kan være static siden den ikke bruker felt eller metoder som er ikke-static. I forhold til innkapsling, hovedgrunnen til å la en konstruktør ta en eller flere parametre : Parametrene til en konstruktør er informasjon som trengs for å gi objektet en gyldig starttilstand, som oftest verdien til felt som må være satt og det ikke finnes noen fornuftig default-verdi. Java ved gitte forutsetninger vil automatisk opprette en konstruktør for en klasse, slik at instanser av klassen kan opprettes, selv om en ikke eksplisitt har definert en konstruktør for klassen. Betingelsen(e) for at Java skal gjøre dette : Forutsetningen for å automatisk opprette en konstruktør er at den er instansierbare dvs. ikke abstrakt, og det ikke er definert noen andre konstruktører. En slik konstruktør vil være public og ha tom parameterliste. En annen teknikk for initialisering enn deklarasjon er å bruke en eller flere konstruktører. Dette er metoder med samme navn som klassen som blir implisitt kalt ved bruk av new (med eller uten argumenter). Fordeler:
    • en kan skrive mer komplisert kode for initialisering, med f.eks. valg og validering
    • en kan (tvinges til å) oppgi argumenter som kan brukes i initialiseringen
    • konstruktører hjelper en å sikre tilstand ved bruk av innkapsling</span> Ulemper:
    • koden for initialisering blir ofte langt unna feltene den initialiserer
    • det er lett å glemme å initialisere felt</span> Dette handler ominnkapsling, som har to aspekter: 1) sikring av gyldig tilstand og 2) skjuling av implementasjonsdetaljer, så koden lettere kan endres uten at andre klasser påvirkes. Av disse to er aspekt 1) viktigst. Den første teknikken krever mindre kode og er derfor enklere å lese og skrive. Siden en bruker final-modifikatoren så sikres aspekt 1), siden verdiene/tilstanden ikke kan endres tross at feltet er public. Imidlertid så er det andre aspektet ved innkapsling ikke ivaretatt, siden feltet er eksponert. Derfor er den andre teknikken å foretrekke, som ivaretar begge aspektene. Objekter som ikke kan endres, kan deles/brukes i flere datastrukturer uten fare for kluss. Hvis delte objekter kan endres, så må en være mer nøye på hva en gjør. finne passende datastruktur og innkapslingsmetoder. En trenger bare én add-metode, selv om det er vist to har. For å gå gjennom segmentene trenger en både én metode for count/size (ellers vet en ikke når iterasjonen skal stoppe) og én for å hente ut et element. Det var veldig mange som lurte på formuleringen ”gå gjennom dem basert på indeks”, antageligvis fordi det ikke var sagt at dette var innkapsling for andre klasser, ikke til bruk internt i klassen.

      Grensesnitt/Interface - Teorispørsmål

  2. Hvilket nøkkelord i Java brukes for å angi at en klasse implementerer et grensesnitt?
  3. Hva er forskjellen på en klasse og et grensesnitt?
  4. Nevn tre bruksområder hvor det er en fordel å bruke grensesnitt.
  5. Kan grensesnitt definere private metoder?
    1. Hvorfor gir/gir ikke dette mening?
  6. Kan et grensesnitt implementere et annet grensesnitt?
  7. Kan et grensesnitt arve et annet grensesnitt?
  8. Hva er forskjellen på å implementere et grensesnitt og å arve fra en annen klasse?
  9. Hva er forskjellen på en abstrakt klasse og et grensesnitt?
    1. Hvilke muligheter har man i en abstrakt klasse som man ikke har i et grensesnitt?
  10. Definer (skriv ned) et grensesnitt etter hukommelsen.
  11. Kan et grensesnitt inneholde variabler?
    1. Hva må eventuelt en variabel deklareres som for å være tillatt i et grensesnitt?
  12. Kan et grensesnitt definere konstruktører? Et grensesnitt skal ikke ha konstruktører og statiske metoder. Det er faktisk lov å ha statiske metoder i grensesnitt i Java, men det hører ikke med i objektorientert tenkning. Dice-grensesnittet utvide (extends) Iterable </span>_og kan_ liste opp metoden(e) fra Iterable. for et grensesnitt kan gjenta metoder fra et grensesnitt det utvider, men må ikke.

    Arv - Teorispørsmål

  13. Hva er forskjellen på en abstrakt klasse og et grensesnitt? 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.
  14. Hvilke krav må en subklasse oppfylle for at det skal være naturlig for den å arve fra en superklasse?
  15. Hvilket nøkkelord brukes for å arve fra en annen klasse i Java?
  16. Hvor mange klasser kan en subklasse arve fra?
  17. Du har tre klasser: Human, Person og Student. Student arver fra Person, og Person arver fra Human. Du lager et objekt av hver klasse: human, person, student.
    1. Har student tilgang til feltene og metodene i Human?
    2. Hva vil “human instanceof Student” returnere?
    3. Hva vil “student instanceof Human” returnere?
  18. Når en subklasse arver fra en superklasse, får den tilgang til konstruktørene til superklassen?
    1. Når får den eventuelt (ikke) det?
  19. Forklar hvordan bruk av protected-felt/metoder fungerer ved arv.
  20. Kan man instansiere en abstrakt klasse? 2 klasser har en del felles egenskaper, som kan samles i en felles superklasse kalt MenuItem: Her er poenget å samle det som er felles. Klassen bør være abstract, siden det ikke gir mening å instansere den.
  21. Er det nødvendig å bruke annotasjonen @Override når man redefinerer en arvet metode?
  22. Du har arvet en metode fra en superklasse og redefinert den i din egen klasse. Hvordan kan man bruke metoden som var definert i superklassen?
  23. Du skriver din egen konstruktør i en subklasse, men ønsker å kalle superklassens konstruktør for å slippe å kopiere kode. Hvordan gjør du dette? - Når en arver så kan en ikke kun arve de metodene en ønsker, men får alle med på kjøpet. Da vil en være nødt til å redefinere alle en ikke trenger/ønsker at skal være tilgjengelig.
    • </span>Det vil ikke alltid være logisk riktig at klassen skal være **instanceof List**</span>
    • </span>(Teknikken kan bare brukes for én slik liste, siden en bare kan arve fra én implementasjonsklasse). super() kaller konstruktøren i superklassen og trengs for å sikre at også superklassens konstruktør blir kjørt. Her er super-klassen implisitt Object-klassen. Hvis linja ikke er med, så vil et tilsvarende kall, altså til en konstruktør uten argumenter, bli lagt til av kompilatoren. Derfor kan vi trygt fjerne linja. Poenget her at siden Course og Meal nå har en felles superklasse, så blir det enklere å ha datastrukturer med begge disse objekttypene i. Dette forenkler både Menu- og Table-klassene. En blir her bedt om å skrive mye kode på nytt, og siden eksamen var digital, så bør det være greit i praksis. Det er greit å bare skrive de delene som blir endret, til nød forklare endringene med tekst. Det disse tre implementasjonene har felles er score-verdien, så denne kan legges i den abstrakte AbstractScorer-klassen, initialiseres ved å kalle super(score) øverst i subklasse-konstruktørene og leses med getScore(): En antar at det finnes ulike klasser for ulike typer fremkomstmidler en kan leie. Hver type skal kunne ha sine egne sett med verdier brukt til prising, f.eks. pris pr. time og pris pr. Tidsforlengelse. Hvordan arvingsmekanismen kan benyttes?
    • Det naturlige er da å lage en abstrakt klasse </span>Vehicle som inneholder priselementer (pris per time, forsentbot etc) og gettere/settere (som getHourRate, setLocation m.m). Ikke alle av disse trenger å være abstrakte, eksempelvis kan det som har med lokasjon å gjøre være implementert i Vehicle. Alle fremkomstmidler (Bike, Hoverboard og slikt) og må så arve denne klassen og kan redefinere metoder for å endre verdiene/logikken som brukes i prisberegningen. Der en i BikeRental (som egentlig burde skifte navn til VehicleRental) refererer til Bike må en nå i stedet referere til den abstrakte klassen Vehicle. Vi lager en abstrakt klasse basert på Trip og gjør estimateTime abstract: Så lar vi Trip arve fra denne og implementere estimateTime som over. Andre varianter (interface) vil gjøre det samme, men implementere estimateTime med annen logikk. Det er strengt tatt ikke nødvendig å ha en abstrakt klasse, en kan alternativt bare arve fra Trip. SimpleTable og CompositeTable blir subklasser av Table, som selv enten blir et grensesnitt eller en abstrakt klasse med i hvertfall getCapacity-metoden. De to variantene er vist under. SimpleTable kan ta over det meste av Table-koden, evt. arve alt. CompositeTable kapsler inn informasjonen fra mergeTable og bruker de to Table-objektene til å beregne kapasiteten. Alternativt kan Table beholde capacity-feltet og getCapacity()-metoden og de to andre initialiserer capacity med super(…). Vi har her ikke lagt opp til at CompositeTable-objekter skal få et løpenummer, men det er greit å la den funksjonaliteten være en del av en abstract Table-(super)klasse. Her er poenget å skille ut det som har med score å gjøre i en abstrakt superklasse, nemlig feltet score, (den delen av) konstruktøren som tar inn og setter score og getScore og setScore. Dette er viktigst.
    • Hvis en lar den abstrakte superklassen implementere Dice-grensesnittet evt. deklarer en del av Dice sine metoder som abstrakte metoder, så kan enda flere metoder implementeres, f.eks. trenger toString og getValueCount bare getDieCount og getDieValue. En trenger ikke implementere disse, men forklare at det går an og vil være lurt.</span>

Delegering - Teorispørsmål

  1. Forklar forholdet mellom en delegat og en delegerende. Delegeringsteknikken i praksis, hvor en instans av (en implementasjon av) et grensesnitt bruker en eller flere andre instanser av (implementasjoner av) samme grensesnitt, til å gjøre jobben.
  2. I et firma med en Manager og flere Coworkers, hvem er naturlig de(n) delegerende og hvem er delegat(er)?
  3. Forklar hvordan det kan være nyttig å benytte grensesnitt i forbindelse med delegering.
    1. Ved å benytte grensesnitt for en delegat-klasse, hvilke fordeler får man?
  4. Når kan det være smart å benytte delegeringsteknikken fremfor arv? Delegering er en teknikk hvor et objekt, videreformidler kall til en ”delegat” når det er behov for delegatens ferdigheter. I dette tilfellet er det viktig at TimeSlot-objektene som ligger i delegaten regnes med i logikken. F.eks. må getTimeSlotAt(…)-metoden sjekke egne TimeSlot-objekter og delegatens og returnere det tidligste av de to. I del 2 ble utregning av kostnad ‘hardkodet’ inn i rentBike. Hvis man skal støtte utregning av kostnader ved hjelp av delegering, så må man lage et grensesnitt (interface) som inneholder en metode for beregning av pris. I LF har vi laget grensesnittet PricePolicy, som blir implementert av klassen DefaultPricePolicy. I del 2 ble all beregning gjort i en egen hjelpemetode, computePrice. Det er derfor helt greit at grensesnittet inneholder nettopp denne metoden. I BikeRental må en så opprette et DefaultPricePolicy-objekt, og kalle dennes computePrice for å beregne pris. Hvis en vil bruke en annen prisingslogikk, så bruker man bare en annen implementasjon.

en kan også bruke delegering for å tilby individuell prising (altså pr. Person), f.eks. Bonus-ordninger: I 3a ble det lagt opp til delegering av kostnadsberegning, men dette var ikke knyttet til enkeltpersoner. For å implementere individuell prising kan en heller lage et sett med ulike PricePolicy, (studenter, barn, voksne etc.) og knyttet disse til Person-objektet (med gettere og settere). Når rentBike skal beregne kostnad må en så delegere til Person-objektet (som igjen ligger i Bike) sin PricePolicy. Delegeringsteknikken er mer fleksibel enn arv, fordi en når som helst kan bytte ut TimeEstimator-objektet og dermed også endre oppførselen til estimateTime-metoden. Arv-mekanismen frigjøres dessuten til andre mer ”verdige” formål. 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.

Observatør-observert-teknikken - Teorispørsmål

  1. På hvilken måte kan observatør-observert-teknikken hjelpe til å holde tilstanden i et program konsistent? Observatør-observert-teknikken brukes når en eller flere objekter, her kalt observatører, må holdes konsistent med et annet objekt, her kalt observert, og det observerte objektet sin implementasjon ikke skal være for tett knyttet til observatørenes. Teknikken er basert på at det observerte objektet sier fra til en eller flere observatører om at tilstanden er endret, slik at observatørene kan oppdatere sin tilstand ift. deres regler for konsistens.
  2. Nevn (minst) tre metoder som burde være med i de aller fleste implementasjoner av observatør-observert-teknikken.
  3. Hvorfor kan det være lurt å benytte grensesnitt for den observerte parten i et observatør-observert-forhold?
  4. Hvorfor kan det være lurt å benytte grensesnitt for observatør-parten i et observatør-observert-forhold?
  5. Tenk at en nettside oppdateres jevnlig, og du ønsker å få et varsel til mobilen din hver gang nettsiden oppdateres. Hvilken av partene er den observerte og og hvilken er observatør?
  6. Hvorfor lønner det seg å bruke observatør-observert-teknikken fremfor å jevnlig sjekke om et objekt har endret tilstand? Dette er standard bruk av observatør-observert-teknikken, altså må en ha felt med liste av lyttere, metoder for å legge til og fjerne lyttere og helst en hjelpemetode for å varsle dem. Observatør-observert-teknikken, lytte til endringer. Lytting med 1) et grensesnitt, 2) liste av lyttere, 3) add/remove-metoder for lyttere, 4) metode til lytterne og 5) kall av metode når endring skjer. Observerbarhet krever:
    1. at en holder styr på lytterne og
    2. at alle endringsmetoder kaller lytternes lyttermetode.</span> Observert-observatør-teknikken brukes til å følge med på hvordan tilstanden til objekter endres over tid, typisk for å sikre konsistens med andre objekter, f.eks. et GUI med “indre” objekter. Et objekt er observerbart dersom en kan lese ut alle relevant tilstand og en kan få beskjed (lytte på hendelser) om når og hvordan denne tilstanden endres. Det siste spørsmålet er et lurespørsmål: Dice er allerede observerbar! Siden tilstanden ikke kan endres, så trenger en ikke å støtte lyttere. Hvis tilstanden kunne endres, så måtte man 1) definert et lyttergrensesnitt, 2) hatt metoder for å administrere (registrere og avregistrere) lyttere, 3) hatt metode(r) for å sende ut varsler om tilstandsendringer og 4) kalt disse metodene i alle metoder som faktisk endrer tilstanden. Den generelle teknikken er observatør-observert, som både brukes for å si fra om at tiden går og om at Trip er endret. Tiden håndteres ved å anta at det finnes en Clock-klasse, som kan si fra hvert minutt til et sett med lyttere. En kan f.eks. ha grensesnittet ClockListener med minuttPassed()-metoden og metodene addClockListener og removeClockListener. Trip må gjøres observerbar: 1) en må ha lese-metoder for alle relevante data og 2) en må kunne registrere lyttere (som implementerer et lyttergrensesnitt f.eks. kalt TripChangedListener) som får beskjed når objektet endres (f.eks. vha. en metode kalt tripChanged). Hva innebærer observerbarhet? Hvordan en gjør en (egenskap i en) klasse observerbar?
      • Observerbarhet handler om å la et eller flere objekter (observatørene/lytterne) få beskjed om endringer i et annet objekt (den observerte). Observert-klassen må administrere et sett med lyttere (objekter som implementerer et lyttergrensesnitt), vha. felt for Collection av lyttere og add/remove-metoder. Alle steder hvor tilstanden (til egenskapen) endres, må det skytes inn kode som sier fra til lytterne (kall på fire-metode, som går gjennom lytterne).
      • Capacity-egenskapen beregnes på bakgrunn av </span>tables- og seatings-listene, og derfor må lytterne varsles hver gang disse endres (av addTable, removeTable, addSeating og removeSeating). Det skilles ikke mellom om kapasiteten øker eller minker, selv om det er økning som GuestManager er interessert i. Lyttere registreres med add/removeCapacityListener-metoder og en fireCapacityChanged-metode, som varsler lytterne (CapacityListener-implementasjoner), kalles av endringsmetodene. Standardteknikken krever et passende lyttergrensesnitt og en liste av lyttere som kalles på passende sted. En bruker gjerne en Collection for å lagre lytterne og add/remove-metoder for å administrere lytterne.
      • LF definerer lyttergrensesnittet </span>TreatmentListener. Dette grensesnittet har én metode, som kalles idet doktor-pasient-koblingen etableres, altså på tidspunktet det passer å gi beskjed til pasienten om hvilken doktor hen skal gå til. Metoden bør ta inn faktisk pasient, doktor og akuttmottak (TreatmentUnit).
      • Endringer som må gjøres i </span>denobserverte, TreatmentUnit, er at alle lytterne lagres i en privateCollection av type TreatmentListener, og at den legger inn relevante public add/remove til denne. I tillegg defineres hjelpemetoden fireTreatmentStarted, som kalles rett etter at setPatient har etablert en ny doktor-pasient-kobling (i både startTreatment(Doctor) og startTreatment(Patient)).

Typer - Teorispørsmål

  1. **Collection strings = new ArrayList()** a) Hva er sammenhengen med typen deklarert på venstre-side og typen deklarert på høyre-side? Hvordan påvirker **String**-spesialiseringen (altså det som står mellom **< >**) bruken av **strings**-variabelen?
    • Typen på høyresiden må være den samme eller en subklasse (inkl. implementasjonsklasse, som her) av typen på venstresiden. Spesialiseringen må være den samme. </span>String-spesialiseringen påvirker parametertyper og returtyper for Collection- og ArrayList-metodene. F.eks. vil get returnere String og add og set-metodene vil ta en String som parameter. b) Hvordan påvirker det videre programmering at det står ?
  2. Hva er casting?
  3. a) Hva er forskjellen på int og Integer? b) Gi et eksempel på når en må bruke Integer.
  4. Hva er typen til følgende uttrykk? a) “Java” + “Gøy”; // String + String = String b) 1/2; // int / int = int c) (“J”+”ava”).charAt(2); // char
  5. Public Eksamen(String besvarelser…){} Hvor mange String-objekter kan sendes inn i denne konstruktøren?
    Man må deklarere typen til alle felt, variabler og parametre (i motsetning f.eks. Python, Javascript og Matlab). De viktigste fordelene er at det blir lettere å:
  6. oppdage/hindre feil bruk av verdier (for verktøy, kompilator og programmerer)
  7. tilby hjelp til kodingen, f.eks. foreslå metoder
  8. kompilere til effektiv kode

At metoder i en klasse kan ha samme navn kalles ”overloading”. For å avgjøre hvilken som skal kalles, brukes de deklarerte typene til argumentene (ikke returverdien). Merk at dette er noe annet enn polymorfi, som handler om at subklasser kan ha ulike implementasjoner av metoder definert i en felles superklasse. Hensikten med å definere en toString()-metode er at toString()-metoden brukes implisitt når Java lager String-objekter av instanser ifm. bruk av + og IO og sikrer at tilstanden til instanser blir presentert på en nyttig måte. Hvordan er klassen kodet så det forklarende ordet/navnet vises ved utskrift, f.eks. med System.out.println(…)? Ved utskrift så brukes implisitt toString()-metoden, som er implementert og returnerer label. Et funksjonelt grensesnitt har bare én abstrakt metode, og resultatet av å utføre metoden skal alltid være det samme for samme argumenter. Dette gjør at man kan tenke på implementasjonen som en matematisk funksjon. Det er også et poeng (men underordnet) at grensensittet er ment å være den primære funksjonen til klassen som implementerer den. Ellers gir det ikke så mye mening å bruke anonyme klasser/lambda-uttrykk til å implementere grensesnittet. Et eksempel på dette er Comparator, som kun implementeres for å sammenligne argumentene. Comparable-derimot, implementeres av dataklasser og er derfor en sekundær funksjon, som det ikke er noe poeng å implementere som primærfunksjon. DiceScorer-grensesnittet er funksjonelt fordi det 1) har kun én abstrakt metode og 2) er ment å være primærtfunksjonen til klassen som implementerer den. 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. CapacityListener-grensesnitt er (teknisk sett) funksjonelt, siden det bare har én (abstrakt) metode (og kan derfor implementeres med lambda-syntaksen). Dette kreves i et svar som får poeng i det hele tatt. I tillegg bør andre argumenter (for at grensesnittet ikke er funksjonelt) trekkes inn, f.eks. at metoden typisk ikke er implementasjonens primære funksjon og at en ikke tenker på den som en matematisk funksjon som kun er avhengig av argumentene. Hjelpemetode tokenize, som kan være nyttig ved innlesing og som kan antas ferdig implementert. Modifikatorer? En slik hjelpemetode bør for det første være markert som private, siden det ikke er naturlig at dette er en tjeneste som tilbys andre klasser. For det andre bør den være markert som static, siden den ikke bruker (leser eller endrer) tilstanden til noe Family-objekt. Eneste grunn til at den ikke skal være static, er hvis en subklasse av Family har behov for å redefinere den, og det er ikke aktuelt her. Her er poenget at en trenger en global teller, som en får til i Java ved bruk av static. Denne må brukes og økes i Table sin konstruktør.

Testing - Teorispørsmål

  1. Hvilke tre “faser” går man gjennom under kjøring av en JUnit-test?
    1. Hva må de ulike metodene i hver “fase” hete?
  2. Hvor mange test- metoder kan en JUnit-test inneholde?
  3. Hva er meningen med testing av programmer på enhetsnivå (Slik JUnit gjør)? Den generelle testeteknikken som JUnit-testing (og JExercise) baserer seg på er å rigge opp objekter med en før-tilstand, endre tilstanden og sammenligne med forventet etter-tilstand. Noen ganger er det ingen før- og etter-tilstand, da en kun sjekker et selvstendig metodekall, dvs. sammenligner returverdi med fasiten.
    • bruk av </span>assertTrue og == og ikke assertEquals for å sjekke for identisk likhet.

For å teste iterasjon med foreach-løkker, må vi bruke metoden som en slik løkke (implisitt) bruker, nemlig iterator()-metoden og Iterator-objektet som denne returnerer. Her brukes objektene som er rigget opp i setUp-metoden.

hvorfor det er mer komplisert å teste rentBike og returnBike enn å teste getRentedBikes. Hvilke aspekter ved disse metodene er det som gjør det mer komplisert?

  • </span>getRentedBikes kan testes ved å sjekke bare returverdier, etter å ha rigget opp diverse objekter, siden metoden ikke har side-effekter. Effekten av rentBike og returnBike er derimot endring av diverse datastrukturer, så disse må sjekkes etterpå.

Unntak/Exception

I metoder som endrer (en verdi i) et objekt, så bør argumenter valideres. Dette må skje før selve endringen og i tilfelle ugyldig(e) verdier så kastes et unntak av typen IllegalArgumentException(…).

En såkalt checked exception er en subklasse av Exception som ikke samtidig er en subklasse av RuntimeException. En slik Exception må deklareres vha. throws og den kallende metoden må enten håndtere unntaket med try/catch eller deklarere det med throws. Checked exception : en Exception som ikke er en RuntimeException er en checked exception. En slik unntakstype krever enten try/catch eller en throws-deklarasjon for å unngå kompileringsfeil.

Metoder som sjekker argumentene sine før de utfører evt. endringer på objektet kalles validering (og er en viktig del av innkapsling). Det er lurt å skille ut valideringen i en egen metode som kalles fra endringsmetodene. Metoden kan være protected, så subklasser kan redefinere og gjenbruke valideringslogikken. Det er vanligst å bruke en såkalt unchecked exception (usjekket unntak), som IllegalArgumentException. throws-deklarasjonen forteller leseren av koden at konstruktørene kan utløse unntak. Siden unntakene er en subklasse av RuntimeException og dermed ikke checked, så er det ikke nødvendige.

Ulike grensesnitt typer

Standard funksjonelle grensesnitt, inkl. **Predicate**, **Consumer**, **Supplier**, **Function<T, R>**, **BiFunction<T1, T2, R>**, **BinaryOperator** og **UnaryOperator** Grensesnitt som **Predicate** er et såkalt _funksjonelt_ grensesnitt, siden det har én metode som (er ment som å) oppfører seg som en matematisk funksjon. Metoden deklareres som **Collection getMatchingPersons(Collection persons, Predicate test)**</span>. Koden kan skrives på (minst) to måter, enten som en én-linjer med **Stream**-teknikken eller med en løkke som tester og legger til en resultat-liste. Kallet gjøres enklest med lambda-notasjonen: **getMatchingPersons(persons, p -> p.getGender() == ’M’ && p.getAge() == 18)** Collection-rammeverket** (Collection-, List-, Set, Map<K, V>-, Iterator- og Iterable-**grensesnittene og** ArrayList- og HashMap<K, V>-klassene)**, inkludert bruk av **<>** i deklarasjoner, såkalte generics Hvis en klasse implementerer **Iterable** så kan en bruke en instans av klassen på høyresiden av :-tegnet i en for-each-løkka, og iterere over alle verdier "inni" objektet. Her betyr det at vi kan skrive ... **for (double v : values) { ... }** Typen bør være et grensesnitt fra **Collection**-rammeverket, enten **Collection** eller **List**, som er spesialisert til element-typen **Course**. Her brukes **Collection**, fordi en ikke trenger andre metoder enn den deklarerer. Her er poenget å velge en type som passer til hvordan feltet brukes og verdien den blir tilordnet. Verdien som tilordnes er av typen **List**</span>, så typen må enten være **List** eller en av dens superklasser, som er **Collection** og **Iterable**. Hvis vi bare trenger metodene i **Collection**, som **List** arver fra, så er det bedre å bruke **Collection** i deklarasjonen. For å kunne iterere med et **Meal**-objekt bak kolonet i en **for**-løkke, så må **Meal**-klassen implementere **Iterable**</span> og derfor ha en **iterator()**-metode som returnerer **Iterator**</span>. Her er poenget å skjønne hvordan tabeller virker (opprettes, leses fra og skrives til) og logikken bak **valueCounters**-feltet, samt hvordan iterere med **Iterator** (**hasNext()** og **next()**) og **Iterable** (**iterator()**). En kan lage en egen klasse som kombinerer mobilnummer og antall minutter i forkant de skal få varsel, eller bruke en **Map<String, Integer>**. Hvis en klasse implementerer **Iterable**, så kan referanser til denne klassen brukes på høyresiden av kolonet i en for-each-løkke, f.eks. for (Person child : person). Se også **getChildren**-koden over. Et (litt mindre relevant, og ikke påkrevd) alternativ er **Iterable.forEach(Consumer)**</span>. Dette er en såkalt **default**-metode (kom ikke frem i vedlegget) som en får gratis når en implementerer **Iterable**. Forklar med tekst og kode sammenhengen mellom for-each-syntaksen, altså den på formen for ( : ) ..., og </span>**Iterable**- og **Iterator**-grensesnittene.

  • Den vanlige måten å gå gjennom en liste med elementer er med kode som den under til venstre. Dette kalles en </span>for-each-løkke, fordi den går gjennom hvert element i lista. Dette er egentlig spesial-syntaks for iterator-basert iterasjon, det er bare det at du aldri ser iteratoren. Funksjonelt sett er for-each-løkka til venstre ekvivalent med den Iterator-baserte løkka under til høyre. Det er egentlig en smaksak hvilken en bruker, men den venstre varianten er å foretrekke fordi den både er enklere å skrive og lese.
  • Hvis en tenker over det, så er det nettopp </span>iterator()-metoden, som er nøkkelen til at for-each-løkka virker. Det holder at stringListe i kode-eksemplet har en slik metode, for at den skal kunne “omskrives” til koden til høyre som den tilsvarer. Denne koblingen mellom for-each-løkka og iterator()-metoden er ikke tilfeldig, og for å gjøre koblingen eksplisitt og mulig å utnytte for klasser utenfor Collection-rammeverket, så er iterator()-metoden definert i et eget grensesnitt ved navn Iterable (i java.lang-pakken, så en slipper egen import-setning). Det er altså fordi en ArrayList implementerer Iterable at en for-each-løkke med en ArrayList virker! Og ArrayList implementerer Iterable fordi den implementerer List, og List utvider Collection som utvider Iterable. Denne koblingen mellom ArrayList og Iterable er illustrert under med et klassediagram: sortering med**Comparable** og** Comparator** Ved å implementere **Comparable**-grensesnittet så kan **Value**-objekter sorteres vha. Java sine innebygde sort-metoder. Dersom **Course**-klassen implementerer **Comparable**, dvs. sammenligning med et annet **Course**-objekt, så kan **Collections.sort**- og **List.sort**-metodene brukes til sortering av **Course**-objekter. **Exam**-objekter skal også kunne sorteres, men på **to måter**! Hvordan begge sorteringene kan støttes og skriv nødvendig kode : I tillegg til **Comparable**, som bygger sorteringsrekkefølgen inn i klassen selv, så kan en implementere en **Comparator**</span>, som er en annen klasse som sammenligne to **Exam**-objekter: **Table** må implementere **Comparable<Table>** for at **sort**-metoden skal kunne brukes og virke. Alternativt kan man lage en (implementasjon av) **Comparator<Table>**, f.eks. med **(t1,t2) -> t1.getCapacity() – t2.other.getCapacity()**. IO med byte- og tegnstrømmer **(InputStream/OutputStream** og**Reader/Writer** med subklasser) og filer
  • InputStream-/OutputStream-klassene håndterer byte-verdier, mens Reader-/Writer-klassene håndterer char-verdier (dvs. implementerer koding av tegn til/fra bytes iht. Unicode-regler).
  • Input/output-metoder kaster ofte IOException, som er en såkalt “checked exception”. Slike brukes gjerne for feil som er utenfor vår kontroll. Disse krever at kode må fange dem opp med try/catch eller deklarere med throws at de kastes videre.
  • Strømmer bruker gjerne ressurser utenfor Java og close()-metoden sikrer at Java samhandler riktig med disse, f.eks. frigjør dem. For å sikre at dette alltid skjer, er det vanlig å ha close()-kallet i en try/finally-blokk.

    Streams

    return allBikes.stream().filter(bike -> bike.getRenter() == null && bike.getLocation().distance(location) <= distance).count();

return allBikes.stream().filter(bike -> bike.getRenter() != null).collect(Collectors.toList());

getRentedBikes().stream().filter(bike -> getStationNearby(bike, 30.0) != null).collect(Collectors.toList());

diceCol.stream().map(Dice::getScore).reduce(0, (n1, n2) -> n1 + n2)

diceCol.stream().mapToInt(Dice::getScore).sum()

Input/Output (IO)

Det viktigste med save-metoden er at den først skriver ut alle linjer av type 1, altså den person-informasjonen som er nødvendig for å lage Person-objektene før foreldre-barn-koblingen etableres. Vi velger å lage en PrintWriter rundt OutputStream-en vi får inn, for å muliggjøre bruke av print og println. Vi kunne brukt en PrintStream, men en Writer anbefales jo for tekst (trekker ikke for bruk av PrintStream). Så skrives alle linjene av type 2 ut. Derfor blir det to iterasjoner over alle medlemmene. Navn får anførselstegn (”) rundt (merk måten ” inkluderes i en String). Her sjekkes det om en person har barn (kan gjøre på mange måter), så det ikke blir linjer med en forelder, men det er strengt tatt ikke definert som et krav (det står ”sequence of names”, og en sekvens kan jo ha bare ett element). Hvis en har linjer med bare én forelder, så er det viktig at load-metoden håndterer det riktig. Det er vanlig at den som setter opp en OutputStream også lukker den, og derfor avslutter vi ikke med pw.close(). Vi avslutter imidlertid med pw.flush() for å sikre at all vår output sendes ut med en gang (trekker ikke for manglende bruk av close()/flush()). load-metoden klassifiserer hver linje som en av de tre typene ved å først sjekke om den er tom eller starter med # (type 3) og så sjekker om første token i en linje er en gyldig Gender (type 1). Ellers er den av type 2. Her gjøres det ingen sjekk på om formatet er korrekt, f.eks. om et barn i en linje av type 2 faktisk er registrert som familiemedlem. Det er kanskje litt uklart hvorvidt og evt. hvordan tokenize håndterer #, så det er greit at den brukes før en sjekker for linjer av type 3. Unntak håndteres ikke av metodene, så de må deklareres med throws. Det er naturlig å bruke IOException, for den utløses ved bruk av InputStream og OutputStream. En kunne brukt Exception or å markere (at vi er klar over) at det er mye som kan gå galt, men det anbefales å bruke den mest spesifikke typen. Vi kunne fanget opp og ignorert unntak, men det kan lett maskere feil vi ønsker å avdekke. public void save(OutputStream out) throws IOException { PrintWriter pw = new PrintWriter(out); pw.flush() } public void load(InputStream in) throws IOException { Scanner scanner = new Scanner(in); scanner.close(); }

FXML

Av FXML-koden kan en lese at det grafiske grensesnittet (appen) knyttes til BikeRental ved hjelp av klassen BikeRentalController. Dette er i tråd med navnekonvensjonene brukt i tidligere eksamener. Det refereres til to tekstfelt som en i koden kobler seg til med henholdsvis @FXML private TextField fromInput og toInput. Likeledes må en lage tre metoder (@FXML private void plus1HourAction, minus1HourAction og rentAction) som kalles når knappene i grensesnittet trykkes inn. Her er vi mest opptatt av: - @FXML-annotasjonene - riktig type og navn for variablene og metoden - at en henter input fra dieCountInput og setter output med diceOutput

Diagrammer

Grovt sett beskriver diagrammer to ulike aspekter ved et program:

  1. Tilstand/oppførsel ved kjøretid, altså hva som skjer når programmet kjøres, f.eks. tilstanden til objektstrukturer. Eksempler er Objektdiagrammer, Objekttilstandsdiagrammer og Sekvensdiagrammer.
  2. Design, altså hvordan programkoden er strukturert, f.eks. hvilke klasser som finnes og hvordan de henger sammen. Klassediagrammer er et eksempel.

PLANTUML

  • Hide circle : hides class/interface/abstract icon

De ulike diagramdelene, altså boksene, strekene/pilene og tegnene (ord, tall og ): Diagrammet er omtrent som et klassediagram, så boksene tilsvarer klasser. Navnet øverst en boks er klassenavnet, mens de andre er egenskaper, dvs. data, som instansene vil ha. Strekene er assosiasjoner (eller relasjoner), som sier noe om hvordan instanser kan kobles sammen. Tallene på enden av strekene angir hvor mange koblinger en instans kan ha ( betyr ubegrenset), såkalt multiplisitet. Pilene sier noe om i hvilken retning en kan følge en kobling, ingen betyr begge retninger, mens én pil betyr bare i den retningen. Egenskapene og assosiasjonene blir typisk til felt med type som passer til multiplisiteten. En må velge hvordan dataene skal kapsles inn, altså konstruktører med parametre, gettere og settere, og evt. add- og remove-metoder og andre metoder.

Objektdiagrammer (objekter med tilstand og koblinger) er en diagramtype som viser tilstanden til et program, ved å illustrere “snapshots” av objektstrukturer. Hva er forskjellen (hensikt og innhold) mellom objektdiagrammer og objekttilstandsdiagrammer? Et objektdiagram viser oppbygning av og koblinger mellom objekter/instanser, og brukes for å illustrere tilstanden til (eller en mulig tilstand til) et program. Et objekttilstandsdiagram viser hvordan slike strukturer endres over tid, ved kall av metoder (typisk endringsmetoder), og brukes til å illustrere (mulig) objektoppførsel og utviklingen av tilstanden til et program.

Objekttilstandsdiagrammer Hvis en tenker på objektdiagrammer som tilstander og kobler dem sammen med transisjoner, så får en objekttilstandsdiagram, som viser hvordan objektstrukturer utvikler seg over tid. Objekttilstandsdiagrammer brukes til å beskrive oppførselen til et objekt. Viktig fordel og ulempe/begrensning.

  • Fordeler: Eksempel på forløp kan være enklere å forstå enn en komplett definisjon i form av regler (invarianter). Diagrammer kan være mer intuitive enn tekst. Det er lett å skrive test-kode basert på diagrammet.
  • Ulemper: Diagrammene blir lett store, hvis de skal dekke alle relevante tilfeller. Det er kun i enkle tilfeller en kan beskrive oppførselen komplett. Sekvensdiagrammer Interaksjonsdiagrammer viser hvordan objekter i en objektstruktur bruker hverandre, dvs. kaller hverandres metoder. Det finnes to varianter: samhandlingsdiagrammer utvider objektdiagrammer med visning av (sekvenser av) metodekall, og sekvensdiagrammer viser metodekall mellom objekter langs en tidslinje.

Klassediagrammer (klasser og assosiasjoner inkl. kardinalitet) viser klassestrukturen til et program, med innholdet i klasser (attributter og operasjoner) og hvordan de er koblet sammen med arv og assosiasjoner.