- Created by Unknown User (hal), last modified by Børge Haugset on 21.06.2019
Her legger vi gradvis ut eksamensoppgaven og løsningsforslag. Under eksamen hadde studentene tilgang til denne javadocen og denne dokumentasjonen. Kildekode legges inn etterhvert, og når sensur er underveis vil vi også legge inn ofte forekommende feil og misforståelser.
På GitHub finner dere full kode til alle deler. Dette er ikke koden akkurat slik den er forventet på eksamen, men viser delene slik vi jobbet dem frem. Også koden på GitHub kan tenkes å endre seg etterhvert, for å bli mer lesbar, bedre eksempelkode for neste års studenter etc.
Eksamensinformasjon
Dersom du mener at opplysninger mangler i en oppgaveformulering, gjør kort rede for de antagelser og forutsetninger som du finner nødvendig. Hvis du i en del er bedt om å implementere klasser og metoder og du ikke klarer det (helt eller delvis), så kan du likevel bruke dem i senere deler. Du trenger aldri å gå tilbake og endre kode basert på kunnskap du får senere, det som står i en oppgavetekst eller vedlegg relatert til deloppgaven er nok. Eventuelle endringer som må gjøres i en metode du tidligere har kodet skal altså kun gjøres i den nåværende oppgaven
Les derimot igjennom hele eksamen så du får en oversikt. Det kan hende at det er bedre for akkurat deg å gjøre ferdig del 5 før du gjør ferdig del 4.
En oversikt over klasser og metoder for alle oppgavene oppgitt til venstre for oppgaveteksten. Denne teksten inneholder krav til de ulike delene, som du må ta hensyn til når du løser oppgavene. I tillegg til metodene som er oppgitt, står du fritt til å definere ekstra metoder for å gjøre løsningen ryddigere.
Hjelpemidler
Du har tilgang til den fullstendige dokumentasjonen av java, samt enklere dokumentasjon av blant annet testing. Du vil finne begge disse vedleggene i bunnen av oppgavesettet. Disse vil åpne seg i en ny fane som du kan ha liggende under hele eksamen.
Oppgavene
Oppgavens tema er behandling av pasienter på et akuttmottak (TreatmentUnit), som håndterer koblingen mellom et sett med pasienter (Patient) og legene (Doctor) som behandler dem. Pasientene er allerede diagnostisert med et sett ulike tilstander. De behandles av leger, men hver lege kan bare behandle et sett med tilstander. Noen pasienter kan ha grunn til å bli prioritert i behandlingskøen, og en lege kan måtte avbryte en pågående behandling for å ta denne pasienten. Legene på mottaket behandler ulike tilstander, så pasienter kan måtte behandles av flere leger før de er friske og kan skrives ut. Når en lege kan ta imot en pasient skal et informasjonspanel vise hvilken pasient som skal gå til hvilken lege.
Skjelettet til klassene og metodene endrer seg mellom deloppgavene. Deloppgavene skisserer et system som gradvis utvides:
- Del 1 er første trinn i systemet, og inneholder enklere logikk enn beskrevet ovenfor.
- Del 2 er mer ferdig, og danner basis for resten av deloppgavene.
- Del 3 løser prioritering av pasienter ved hjelp av arv.
- Del 4 løser prioritering av pasienter ved hjelp av delegering. Merk at deloppgavene 3 og 4 altså løser det samme problemet på ulike måter.
- Del 5 bruker lytting for å vise informasjon i et panel laget ved hjelp av FXML.
Denne første versjonen av systemet består av tre klasser. Patient er en tom klasse. Doctor-klassen har en assosiasjon til en Patient. TreatmentUnit holder rede på hvilke pasienter og doktorer som finnes, og styrer avvikling av behandlingskøen. Pasienter som har blitt behandlet av en lege fjernes fra systemet. Pasientene har altså ingen (helse)tilstander her.
/*Er klar over mellomrom mellom linjene, men det kommer av kopiering fra Inspera (for å få farger...)*/
/** * A doctor has the capacity to treat one patient at a time. */ public class Doctor { // Internal variables go here: /** * @return the patient this doctor is treating, or null if s/he isn't currently treating any patient. */ public Patient getPatient() { // 1a ... } /** * @return true if this doctor is currently treating a patient, otherwise false. */ public boolean isAvailable() { // 1a ... } /** * Sets the patient that this doctor is treating, use null to indicate s/he isn't currently treating any patient. * @param patient */ public void setPatient(final Patient patient) { // 1a ... } } /** * A class for managing a set of doctors and the patients they're treating. * When doctors or patients arrive, it is made sure that patients are treated as soon as possible. */ public class TreatmentUnit { // Internal variables go here: // 1b /** * Adds a doctor and makes sure s/he starts treating a patient, if one is waiting. * @param doctor */ public void addDoctor(final Doctor doctor) { // 1b ... } /** * @return the currently available doctors */ public Collection<Doctor> getAvailableDoctors() { // 1b ... } /** * Adds a patient to this treatment unit, and makes sure treatment starts if any doctor is available. * Otherwise the patient is queued for treatment when a doctor becomes available. * @param patient */ public void addPatient(final Patient patient) { // 1b ... } /** * @param pred the predicate that the doctor must satisfy * @return some doctor satisfying the predicate */ public Doctor getDoctor(final Predicate<Doctor> pred) { // 1b ... } /** * Find the doctor, if any, that treats the provided patient. * @param patient * @return the doctor treating the provided patient, or null, of the patient isn't currently being treated */ public Doctor getDoctor(final Patient patient) { // 1b ... } /** * Find all patients that are not currently being treated. * @return the patients not currently being treated. */ public Collection<Patient> getWaitingPatients() { // 1b final Collection<Patient> result = new ArrayList<>(); ... } /** * Finds a waiting patient and sets him/her as the provided doctor's patient. * @param doctor the doctor for which a patient to treat should be found * @return true if a patient for the provided doctor was found, false * otherwise. */ private boolean startTreatment(final Doctor doctor) { // 1c ... } /** * Finds an available doctor for the provided patient, and sets that doctor to * treat the patient. * @param patient the patient for which a treating doctor should be found. * @return true if a doctor for the provided patient was found, false * otherwise. */ private boolean startTreatment(final Patient patient) { // 1c ... } /** * Removes the link between doctor and patient, after treatment is finished. * Since the patient is fully treated, s/he is removed from this treatment * unit. * Also ensure the doctor starts treating another patient. * @param doctor the doctor that has finished treating his/her patient. */ public void treatmentFinished(final Doctor doctor) { // 1c ... }
Oppgave a)
Skriv ferdig Doctor-klassen i henhold til skjelettet, altså nødvendige innkapslingsmetoder og isAvailable. Patient er så langt en tom klasse, du trenger ikke implementere denne.
Doctor-klassen er i 1a brukt for å vise grunnleggende forståelse av innkapsling, bruk av interne variable og metoder. Det legges ikke vekt på bruk av final, verken her eller i eksamen forøvrig.
Den interne variable variabelen patient angir hvilken (om noen) pasient som er under av denne doktoren nå. Den bør kapsles inn, altså gjøres private, samt ha vanlige public get/set-metoder. Metoden isAvailable() skal returnere hvorvidt patient for tiden er null eller ikke. Vi trenger ingen konstruktør, da det ikke er naturlig at doktorer behandler en pasient allerede ved opprettelsen.
public class Doctor { private Patient patient; // 1a public Patient getPatient() { // 1a return patient; } public boolean isAvailable() { // 1a return getPatient() == null; } public void setPatient(Patient patient) { // 1a this.patient = patient; } }
Merk at javadoc-en for isAvailable var motsatt av logikken vi ønsket, derfor trekkes det ikke om logikken er snudd.
Vanlige feil:
- Feil synlighetsmodifikatorer.
- Implementasjon av løsning for senere oppgaver (dette ble det eksplisitt forklart at en ikke skulle gjøre, og kommentaren gjelder også for de andre oppgave i 1.
- Mange la inn en ekstra boolean-variabel for om en doktor var ledig eller ikke. Dette kan i og for seg fungere, selv om det er keitete. Noen av de som valgte en slik løsning glemte å oppdatere denne variabelen når en pasient sjekkes.
- En del sjekket i setPatient om doktoren allerede hadde en pasient. Ikke en stor feil, men dokumentasjonen sa ikke noe om dette. Dette ville også skape større endringer i senere oppgaver.
- Vi trakk ikke for misforhold mellom dokumentasjon og navn på isAvailable. Positiv og negativ løsning lik.
Oppgave b)
Skriv følgende deler av klassen TreatmentUnit, basert på beskrivelsen i skjelettet:
- Avgjør og implementer den interne representasjonen av pasienter og doktorer.
- addDoctor, addPatient, getAvailableDoctors, og getWaitingPatients.
- getDoctor: Denne finnes i to versjoner, med og uten bruk av Predikat. Du skal skrive begge disse versjonene.
Vær obs på at enkelte av disse metodene bør kalle startTreatment fra 1c.
En TreatmentUnit har behov for å lagre en samling med doktorer som jobber der og pasienter som skal behandles (eller er under behandling). Vi har valgt vi en Collection for å lagre hver av disse, fordi det ikke er behov for indeksbasert tilgang til elementene. En annen (kanskje Set-basert) klasse som beholdt rekkefølge kunne også vært brukt). Merk at vi ikke har egne lister for doktorer som er ledige eller pasienter som venter, selv om det ville gjort noen ting enklere.
Variable skal være private, med begrenset tilgang gjennom public-metoder.
Det er to getDoctor-metoder, hvor getDoctor(Predicate) gjør et søk gjennom alle doktorer, og returnerer den første (om noen) som tilfredsstiller Predicate-parameteret ved kall til dennes test-metode. Den andre metoden getDoktor-metoden, getDoctor(Patient), bruker den første ved å gi inn en lambda (instans av Predicate-grensesnittet) som sjekker om doktoren har registrert denne pasienten som sin.
getAvailableDoctors og getWaitingPatients er nokså like ved at de returnerer en samling objekter fra et større sett, som tilfredsstiller gitte kriterier. Disse kan forenkles hvis en har egne lister for dem, men da blir andre metoder mer kompliserte.
public class TreatmentUnit { // Internal - 1b private Collection<Doctor> doctors = new ArrayList<>(); private Collection<Patient> patients = new ArrayList<>(); public void addDoctor(Doctor doctor) { // 1b doctors.add(doctor); startTreatment(doctor); } public Collection<Doctor> getAvailableDoctors() { // 1b Collection<Doctor> result = new ArrayList<>(); for (Doctor doctor : doctors) { if (doctor.isAvailable()) { result.add(doctor); } } return result; // return doctors.stream().filter(Doctor::isAvailable).collect(Collectors.toList()); } public void addPatient(Patient patient) { // 1b patients.add(patient); startTreatment(patient); } public Doctor getDoctor(Predicate<Doctor> pred) { // 1b for (Doctor doctor : doctors) { if (pred.test(doctor)) { return doctor; } } return null; } public Doctor getDoctor(Patient patient) { // 1b return getDoctor(doctor -> doctor.getPatient() == patient); } public Collection<Patient> getWaitingPatients() { // 1b Collection<Patient> result = new ArrayList<>(); for (Patient patient : patients) { if (getDoctor(patient) == null) { result.add(patient); } } return result; }
Valinge feil /alternative løsninger:
- Bruk av liste for å holde styr på pasienter og doktorer. En del av dere valgte dette, og laget så løsninger i senere oppgaver der pasienter ble sortert etter prioritet. Vi har forsøkt å holde følge med løsningen deres, og sett senere oppgaver i sammenheng med valg som ble gjort tidligere. Det er derimot ikke nødvendig å ha en liste for å løse oppgaven.
- Sjekk for om en samling var lik null for å se om den var tom, i stedet for om samlingen var size 0 eller isEmpty. Dette gjelder også i andre oppgaver.
- Mange valgte en løsning der listen over pasienter kun inneholdt pasientene som ikke var under behandling (se i sammenheng med neste punkt). Dette gjorde det litt mer komplisert å holde styr på hvilke pasienter som skulle være hvor, også i senere oppgaver.
- addDoctor/addPatient skulle kalle startTreatment. Mange valgte å implementere mye av det startTreatment skulle gjøre inn i disse to metodene.
- Mange sleit med predikatmetoden.
- Det ble ikke mye trukket dersom en i getDoctor(patient) brukte sin egen sjekk i stedet for å kalle predikatmetoden.
Oppgave c) - TreatmentUnit: Koble pasient og doktor
Hver gang en ny pasient eller lege er lagt til, eller en lege har avsluttet en behandling, bør TreatmentUnit forsøke å koble en ledig lege og en pasient som skal behandles. Implementer de to startTreatment-metodene og treatmentFinished (sistnevnte brukes ikke i denne underoppgaven, men senere).
startTreatment(Doctor) finner en pasient å tilordne en doktor (som har kommet på jobb eller er blitt ledig), ved å velge første (om noen) av de som venter på behandling (getWaitingPatients). Finner vi en pasient, settes doktoren sin pasient og true returneres, eller returneres false.
startTreatment(Patient): Denne likner veldig på den første, men er fra en pasient sitt perspektiv. Her tas det utgangspunkt i ledige doktorer (getAvailableDoctors). Finnes ingen ledig doktor returneres false, ellers settes den første doktoren i samlingen til pasienten det er snakk om, og true returneres. Her kan ville det vært en fordel om getAvailableDoctors returnerte en liste, siden vi trenger å ta ut element på en bestemt indeks (0).
treatmentFinished(Doctor): Vi har valgt å utløse et unntak av type IllegalStateException hvis doktoren ikke har noen til behandling. Koblingen mellom doktor og pasienten fjernes setPatient(null)-kallet. Siden pasientene i del 1 ikke har noen (helse)tilstander slik de får i del 2, er de ferdig behandlet og kan da fjernes fra systemet med patients.remove(patient). Og siden doktoren nå blir ledig, så trigges behandling av en ny pasient med et kall til startTreatment.
private boolean startTreatment(Doctor doctor) { // 1c Collection<Patient> waitingPatients = getWaitingPatients(); if (waitingPatients.isEmpty()) { return false; } doctor.setPatient(waitingPatients.iterator().next()); return true; } private boolean startTreatment(Patient patient) { // 1c Collection<Doctor> availableDoctors = getAvailableDoctors(); if (availableDoctors.isEmpty()) { return false; } Doctor doctor = availableDoctors.iterator().next(); doctor.setPatient(patient); return true; } public void treatmentFinished(Doctor doctor) { // 1c if (doctor.getPatient() == null) { throw new IllegalStateException(doctor + " has no patient!"); } Patient patient = doctor.getPatient(); doctor.setPatient(null); patients.remove(patient); startTreatment(doctor); }
Vanlige feil/alternative løsninger:
- Manglende bruk av getWaitingPatients/getAvailableDoctors i startTreatment.
- Returverdier
- treatmentFinished: ikke sette doktoren sin pasient til null, ikke fjerne pasient fra listen over pasienter (hvis en ikke valgte alternativ løsning nevnt i forrige oppgave, da var dette unødvendig), glemme å kalle startTreatment(doctor) fordi doktoren nå kanskje kan behandle en ny pasient.
Alle pasienter har på forhånd blitt diagnostisert med en eller flere (helse)tilstander (conditions) som må behandles. Tilsvarende har alle doktorer et sett med tilstander som de er kompetente til å behandle. En doktor kan ikke behandle pasienter den ikke har kompetanse til å behandle, og en pasient må være i systemet helt til alle tilstander er behandlet.
/** * A patient has a set of (health) conditions (of type String) that needs to be treated. * Supports iterating over the patient's conditions. */ public class Patient { // Add fields, constructors, and methods here: // 2a // Support iteration // 2a /** * Indicates if this patient has conditions that needs to be treated. * @return true if this patient has conditions that needs to be treated, * false otherwise. */ public boolean requiresTreatment() { // 2a ... } } /** * A doctor has the capacity to treat one patient at a time. * The doctor as a list of competencies (of type String) that * indicates what conditions s/he can treat. */ public class Doctor { // Internal variables go here // 2b /** * Initialise this doctor with a set of competencies. * @param competencies */ public Doctor(...) { // 2b ... } /** * Indicates to what extent this doctor can treat the provided patient. * The value is the number of the patient's conditions this doctor * can treat divided by the number of conditions the patient has. * Conditions and competences are matched using simple String comparison. * @param patient * @return the ratio of the patient's conditions that this * doctor can treat. */ public double canTreat(final Patient patient) { // 2b ... } /** * "Treats" the patient by removing all the patient's conditions * that this doctor can treat. */ public void treat() { // 2b ... } /** * @return the patient this doctor is treating, or null if s/he * isn't currently treating any patient. */ public Patient getPatient() { // Implementation hidden } /** * @return true if this doctor is currently treating a patient, otherwise * false. */ public boolean isAvailable() { // Implementation hidden } /** * Sets the patient that this doctor is treating, use null to indicate * s/he isn't currently treating any patient. * @param patient */ public void setPatient(final Patient patient) { // Implementation hidden } } /** * A class for managing a set of doctors and the patients they're treating. * When doctors or patients arrive, it is made sure that patients are treated * as soon as possible. */ public class TreatmentUnit { // Internal declaration hidden /** * Adds a doctor and makes sure s/he starts treating a patient, if one * is waiting. * @param doctor */ public void addDoctor(final Doctor doctor) { // Possible changes } /** * @return the currently available doctors */ public Collection<Doctor> getAvailableDoctors() { // Possible changes } /** * Adds a patient to this treatment unit, and makes sure treatment starts * if any doctor is available. * Otherwise the patient is queued for treatment when a doctor becomes * available. * @param patient */ public void addPatient(final Patient patient) { // Possible changes } /** * @param pred the predicate that the doctor must satisfy * @return some doctor satisfying the predicate */ public Doctor getDoctor(final Predicate<Doctor> pred) { // Possible changes } /** * Find the doctor, if any, that treats the provided patient. * @param patient * @return the doctor treating the provided patient, or null if the * patient isn't currently being treated. */ public Doctor getDoctor(final Patient patient) { // Possible changes } /** * Find all patients that are not currently being treated * @return the patients not currently being treated */ public Collection<Patient> getWaitingPatients() { // Possible changes } /** * Finds a waiting patient and sets him/her as the provided doctor's patient. * Will only accept a patient that has some condition that the doctor actually * can treat. * @param doctor the doctor for which a patient to treat should be found * @return true if a patient for the provided doctor was found, false * otherwise. */ private boolean startTreatment(final Doctor doctor) { // Possible changes } /** * Finds an available doctor for the provided patient, and sets that * doctor to treat the patient. * Will only accept a doctor that actually can treat some condition for the * provided patient. * @param patient the patient for which a treating doctor should be found * @return true if a doctor for the provided patient was found, false * otherwise. */ private boolean startTreatment(final Patient patient) { // Possible changes } /** * Removes the link between doctor and patient, after treatment is finished. * If the patient is fully treated, s/he is removed from this treatment unit, * otherwise another round of treatment is initiated. * Also ensure the doctor starts treating another patient. * @param doctor the doctor that has finished treating his/her patient */ public void treatmentFinished(final Doctor doctor) { // Possible changes } } /** * Used to test TreatmentUnit */ public class TreatmentUnitTest { private TreatmentUnit treatmentUnit; @Before public void setUp() { ... } @Test public void testAddDoctorsPatient() { final Doctor doctor1 = new Doctor(...); // new doctor can treat "flu" final Doctor doctor2 = new Doctor(); // new doctor can treat "noseblead" and "pneumonia" treatmentUnit.addDoctor(doctor1); treatmentUnit.addDoctor(doctor2); // Test that both doctors are available. ... final Patient patient = new Patient(); patient.addConditions(...); // patient has conditions "flu" and "noseblead" // 2e) start sequence diagram treatmentUnit.addPatient(patient); // Test that only one of the doctors are available: ... Doctor patientDoctor = treatmentUnit.getDoctor(patient); patientDoctor.treat(); treatmentUnit.treatmentFinished(patientDoctor); // 2e) end sequence diagram // Test that the previous doctor is available and that a // new doctor has been assigned to the patient. ... patientDoctor = treatmentUnit.getDoctor(patient); patientDoctor.treat(); treatmentUnit.treatmentFinished(patientDoctor); // Test that both doctors are available: ... } }
Oppgave 2a) - Patient
Implementer følgende deler av Patient-klassen i henhold til skjelettet:
- Bestem selv hvilke felt, konstruktører og metoder du trenger for håndtering av (helse)tilstander (conditions).
- Begrunn valget av løsningen, spesielt hvilke typer du har valgt å bruke.
- requiresTreatment: returnerer om pasienten trenger behandling.
- Legg i tillegg inn støtte som lar andre klasser iterere over pasientens tilstander, på enklest mulig måte.
- VIKTIG: Merk at koden du skriver i oppgave 2b og den oppgitte testkoden i oppgave 2d skal passe til dine valg.
Vi velger igjen Collection, fordi vi ikke trenger indeksbasert tilgang til conditions. Det kan derimot også gjerne brukes et sett (Set-grensesnittet og HashSet-implementasjonsklassen).
Vi har valgt å ha en tom konstruktør og en addConditions-metoden. Man kunne gjerne hatt en konstruktør som tar inn tilstander i stedet. En trenger jo kanskje ikke trenger å legge til tilstander underveis i behandlingsforløpet, bare fjerne dem (removeConditions). Merk dog at oppgaveteksten sier at en bør møte kall definert i testen i 2d som viser en tom konstruktør og Patient.addConditions(...). Hvis en skal ha en konstruktør med parameter bør en dermed også ha en tom for å fylle dette kravet. Kun konstruktør med parameter vil dermed bare være en nesten fullgod løsning.
addConditions(…): Denne skal ta inn en samling, og vi har valgt å bruke varargs, i stedet for å ta inn en Collection.
requiresTreatment(): Pasienten trenger behandling dersom conditions ikke er tom.
removeConditions(…): Denne fjerner de angitte tilstandene, og vi valgte å ta inn en Collection, da det er praktisk både der den brukes og for implementasjonen.
Implements Iterable og iterator()-metoden: Disse gir støtte for å iterere over tilstandene til en pasient, og hente ut en og en tilstand, for eksempel i en for-løkke. Dette ivaretar innkapsling, siden den underliggende lista ikke eksponeres (mer enn nødvendig).
public class Patient implements Iterable<String> { private Collection<String> conditions = new ArrayList<>(); // 2a public void addConditions(String... conditions) { // 2a this.conditions.addAll(Arrays.asList(conditions)); } public boolean requiresTreatment( ) { // 2a return ! conditions.isEmpty(); } public void removeConditions(Collection<String> conditions) { // 2a this.conditions.removeAll(conditions); } @Override public Iterator<String> iterator() { // 2a return conditions.iterator(); } }
Vanlige feil / alternative løsninger:
- Konstruktør med conditions-parametre. Dette er en ganske god løsning, da oppgaveteksten ikke spesifiserer at en doktor for eksempel kan finne ut at noe mer er galt hos pasienten og helsetilstander dermed må legges til. En slik konstruktør bryter derimot med testkoden spesifisert i 2d. En bør i så fall ha begge typene konstruktører, samt Patient.addConditions().
- addConditions: tilstandsparameter ble ikke håndtert korrekt, for eksempel gjennom this.conditions.add(conditions). Manglende kopiering som gjør at eksterne objekter i prinsippet kan endre tilstander.
- Ikke implementert Iterable, la Iterator-metoden gå igjennom conditions med next i stedet for å returnere iteratoren.
- requiresTreatment: sjekk mot null i stedet for om samlingen er tom.
- Opprette en kobling til en doktor inni en pasient. Det er ikke nødvendig med en slik toveis kobling, det skaper også større muligheter for feil senere.
- removeConditions: Ulike mer eller mindre riktige måter å fjerne slike. Denne ble også gjerne glemt ved senere kall fra TreatmentUnit, da ble heller patient.conditions.remove(condition) eller liknende brukt. Dette bryter innkapsling.
Oppgave 2b) - Doctor
Implementer følgende deler av Doctor-klassen i henhold til skjelettet:
- Avgjør intern lagring av tilstander legen kan behandle. Du trenger ikke argumentere for valget.
- Konstruktør: Tenk spesielt på innkapsling av data for å hindre endring fra utenfor klassen. Velg selv parameterliste.
- canTreat: For en gitt pasient skal metoden returnere andelen av dennes tilstander som doktoren kan behandle. Hvis Jens har fem tilstander, og Dr. Who kan behandle tre av dem, så skal metoden returne tallet 0.6.
- treat: For en gitt pasient fjernes de tilstandene som doktoren kan behandle.
Intern lagring av tilstander en doktor kan behandle håndteres greit med en Collection, List er unødvendig jamfør samme problemstilling som i Patient.
Konstruktør: Den skal håndtere et sett med tilstander doktoren kan kurere, som er av typen String. Her er det enda mer naturlig enn for Patient å bruke konstruktøren til å sette tilstandene, siden det i hvert fall er lite behov for å endre kompetansen til doktoren i løpet av et behandlingsforløpet. Merk at Arrays.asList gir en Collection som ikke kan endre størrelse.
canTreat(): Gå gjennom alle tilstandene pasienten har (utnytter at Patient implementerer Iterable), og teller dem og hvilke av dem denne doktoren kan behandle. Merk hvordan telleren må gjøres om til double før divisjonen, så resultatet ikke avkortes til 0.
treat(): Denne metoden gjelder pasienten som er under behandling (satt med setPatient) og fjerner alle tilstander hos pasienten som denne doktoren kan behandle. Dette krever en egnet removeConditions-metode i Patient, detaljene kan variere.
public class Doctor { private Collection<String> competencies; // 2b public Doctor(String... competencies) { // 2b this.competencies = Arrays.asList(competencies); } public double canTreat(Patient patient) { int count = 0, total = 0; for (String item : patient) { total++; if (competencies.contains(item)) { count++; } } return ((double) count) / total; } public void treat() { patient.removeConditions(competencies); } }
Vanlige feil/alternative løsninger:
- Håndtering av conditions-parameter i konstruktør som i forrige oppgave.
- canTreat: feil i løkker og sjekk av likhet mellom tilstander pasienter har og tilstander doktoren kan kurere. Feil returtype.
- treat: Feil ved gjennomgang av tilstander for å fjerne dem, blant annet gjennom patient.conditions.remove() siden denne bryter innkapsling.
Oppgave 2c - TreatmentUnit
Nå som pasienter har ulike tilstander, og doktorer kan behandle slike tilstander, må dette taes hensyn til i klassen TreatmentUnit. En doktor kan ikke behandle pasienter den ikke har kompetanse til å behandle, og en pasient må være i systemet helt til alle tilstander er behandlet.
- Implementer endring i TreatmentUnit i relevante metoder. Du trenger ikke kopiere inn metoder som ikke endrer seg fra del 1, du skal heller ikke endre svar i del 1.
De eneste metodene som blir påvirket, og dermed må endres, er de to startTreatment-metodene og treatmentFinished. Her må en passe på å respektere dokumentasjonen når det gjelder returverdier som true og false.
startTreatment(Doctor): Som før går en gjennom alle pasientene som trenger behandling (getWaitingPatients), men nå sjekker vi også om doktoren faktisk har kompetanse til å gjøre noe med pasientens tilstander vha. et kall til doctor.canTreat(patient). Vi prøver ikke å finne pasienten med størst behandlingspotensiale, altså størst canTreat-resultat.
startTreatment(Patient): Vi går som tidligere gjennom alle de ledige doktorer (getAvailableDoctors), og som over, sjekker vi en ledig doktor har relevant kompetanse (canTreat). Her gjør vi heller ingenting for å finne beste doktor for denne pasientens tilstander.
treatmentFinished(): Forskjellen fra del 1 er at vi nå sjekker om pasienten er ferdigbehandlet eller trenger mer behandling (requiresTreatment). Hvis så er tilfelle trigger vi (forsøk på) behandling med startTreatment(patient), ellers fjernes pasienten fra pasientlista. Som før så får doktoren som er ferdig (muligens) en ny pasient vha. startTreatment(doktor).
private boolean startTreatment(Doctor doctor) { // 2c for (Patient patient : getWaitingPatients()) { if (doctor.canTreat(patient) > 0.0) { doctor.setPatient(patient); return true; } } return false; } private boolean startTreatment(Patient patient) { // 2c for (Doctor doctor : getAvailableDoctors()) { if (doctor.canTreat(patient) > 0.0) { doctor.setPatient(patient); return true; } } return false; } public void treatmentFinished(Doctor doctor) { // 2c if (doctor.getPatient() == null) { throw new IllegalStateException(doctor + " has no patient!"); } Patient patient = doctor.getPatient(); doctor.setPatient(null); if (patient.requiresTreatment()) { startTreatment(patient); } else { patients.remove(patient); } startTreatment(doctor); }
Vanlige feil/alternative løsninger:
- startTreatment: Mange valgte å kalle treat() eller på andre måter kurere pasientene inni sT. Disse skulle bare starte behandling, altså koble lege mot doktor hvis mulig.
- sT: feil ved bruk av canTreat og getWaitingPatients/getAvailableDoctors.
- Noen valgte å inkludere prioritering her. Dette er ikke en del av oppgaven, alt en spurte om var å finne en lege som kunne behandle en pasient.
- Returverdier
- treatmentFinished: manglende fjerning av kobling til pasient i doktor, manglende sjekk av om pasienten trenger ny behandling før den fjernes fra samlingen, manglende kall av startTreatment(doctor) (denne doktoren er klar til dyst).
- Løsningene vil se litt annerledes ut for de som kun la pasienter som ikke er under behandling i samlingen. Pasientene som er under behandling vil da kun eksistere i nåværende doktor. Hvis treatmentFinished finner at pasienten fremdeles trenger behandling, men en ikke finner en ny doktor, så må pasienten legges inn i samlingen igjen.
Oppgave 2d - Testing
Du har fått utdelt et skjelett med halvferdige testmetoder (TreatmentUnitTest). Gjør testmetodene fullstendige i henhold til kommentarene. Du vil finne dokumentasjon av testing i vedlegget nederst på siden.
LF beskriver testene på en kompakt måte, det er ingen ting i veien for å dele dem opp litt. For øvrig er det lite å bemerke ved løsningen.
public class TreatmentUnitTest { private TreatmentUnit treatmentUnit; @Before public void setUp() { treatmentUnit = new TreatmentUnit(); } @Test public void testAddDoctorsPatient() { Doctor doctor1 = new Doctor("flu"); Doctor doctor2 = new Doctor("noseblead"); treatmentUnit.addDoctor(doctor1); treatmentUnit.addDoctor(doctor2); assertTrue(doctor1.isAvailable() && doctor2.isAvailable()); Patient patient = new Patient(); patient.addConditions("flu", "noseblead"); // 2e) start sequence diagram treatmentUnit.addPatient(patient); assertTrue(! (doctor1.isAvailable() && doctor2.isAvailable())); Doctor patientDoctor = treatmentUnit.getDoctor(patient); patientDoctor.treat(); treatmentUnit.treatmentFinished(patientDoctor); // 2e) end sequence diagram assertTrue(patientDoctor.isAvailable()); assertNotSame(patientDoctor, treatmentUnit.getDoctor(patient)); patientDoctor = treatmentUnit.getDoctor(patient); patientDoctor.treat(); treatmentUnit.treatmentFinished(patientDoctor); assertTrue(doctor1.isAvailable() && doctor2.isAvailable()); } }
Vanlige feil/alternative løsninger:
- Feil i bruk av assert. Det er delvis gitt poeng hvis sensor har klart å se at dere har vært 'inne på noe'.
- Det var spesielt mye feil ved den tredje testen, dvs den etter treatmentFinished. Her skulle en sjekke at patientDoctor (den som kurerte sist) og den som kurerer nå (fordi treatmentFinished automatisk kaller startTreatment og pasienten dermed knyttes til den nye doktoren).
Oppgave 2e) - Sekvensdiagram
Tegn sekvensdiagram av det som skjer mellom start sequence diagram- og end sequence diagram-kommentarene i testklassen i skjelettkoden. Diagrammet skal inkludere testen selv, akuttmottaket, pasienten og doktoren som (i den delen av testen) deltar i behandlingen. Du skal ikke ha med kode du legger til selv (f.eks. kall til assert-metoder), som svar på 2 d).
Det skulle være omtrent 8-10 kall til metoder i denne delen.
Løsningsforslaget beskriver hele flyten, og inkluderer kall og objekter som oppstår som et resultat av treatmentFinished(). Kommentaren om antall kall var litt misvisende, siden det var meningen å flytte end sequence diagram-markøren én linje opp, altså over kallet til treatmentFinished(). Løsningen er derfor mer omfattende enn antydet, noe vi må ta hensyn til ved sensuren.
Vanlige feil/alternative løsninger:
- Her var det mange som tegnet objektdiagram, tilstandsdiagram eller objekttilstandsdiagram. Sekvensdiagram har ikke blitt gitt så ofte på eksamen før, men det er en viktig diagramtype å kunne. Feil type har ikke gitt poeng.
- Det var manglende nedbryting av metoder, der disse kaller nye metoder.
- Manglende objekter, mange hadde bare to-tre objekter i sekvensen. Oppgaveteksten var svætr spesifikk når det gjaldt hvilke objekter som skulle inkluderes.
- På grunn av misvisende antall kall (se over tegningen) var vi ellers ganske snille når det gjaldt å gi poeng på delvis løsning. Tallet gjaldt kallene til og uten treatmentFinished.
Fokus i del 3 er organisering av objektorientert kode med arv. Oppgaven kan derfor løses ved hjelp av pseudokode.
Systemet skal støtte tre ulike logikker for å knytte doktor til pasient:
- Pasienter knyttes til den første (men ikke nødvendigvis beste) doktoren som kan behandle en eller flere tilstander hos pasienten. (TreatmentUnit)
- Pasientene kan ha tilstander med varierende kritikalitet, som gjør at de prioriteres foran resten av køen når en doktor er ledig. Gjør nødvendige antakelser om en getPriority-metode som brukes til prioritering. Per, som har en mer alvorlig skade enn Jens, vil altså behandles raskere selv om han kommer inn senere. (Såfremt ikke Jens allerede har startet behandling.) (PriorityTreatmentUnit)
- Pågående pasientbehandling (selv om den er prioritert som over) skal kunne avbrytes og doktoren skal starte behandling av en ny pasient. Dette skal skje dersom det kommer inn en ny pasient, og dennes prioritet er høyere enn hos den som er til behandling. Du kan også her forutsette at det finnes en getPriority-metode i Patient. Anta at Jens er under behandling hos doktor Who. Ida kommer til behandling, har en tilstand med høyere prioritet enn Jens, og kan kun behandles av Who. Jens sin behandling blir derfor satt på vent, og den samme doktoren begynner behandling av Ida. (EmergencyPriorityTreatmentUnit)
I denne deloppgaven skal du beskrive eller implementere støtte for disse egenskapene ved å bruke arv. Du finner ikke noe nytt skjelett til denne oppgaven, men du vil finne skjelettet fra del 2 for oppslag.
Vi ønsker å ende opp med tre klasser, TreatmentUnit, PriorityTreatmentUnit og EmergencyPriorityTreatmentUnit, som implementerer hver sin logikk i lista over. Forskjellen dem i mellom skal være hvordan de implementerer startTreatment-metodene. De skal være knyttet sammen med arv for å gjøre det enkelt for andre klasser å bytte mellom dem og for å gi god gjenbruk, men detaljene i hvordan arvingsmekanismen brukes skal være opp til deg. Det er lov å innføre ekstra klasser og metoder i arvingshierarkiet, hvis det gjør løsningen bedre.
Forklar med tekst og kode hvordan du vil 1) strukturere arvingshierarkiet og 2) hvilke metoder som deklareres/implementeres hvor. Skriv også kode for metodene. Siden fokuset her er mer på objektorientert organisering av kode, vil det også gis poeng for pseudokode.
/** * A patient has a set of conditions (of type String) that needs to be treated. */ public class Patient { /** * Indicates if this patient has conditions that needs to be treated. * @return true if this patient has conditions that needs to be treated, * false otherwise. */ public boolean requiresTreatment() { // 2a // Implementation hidden } } /** * A doctor has the capacity to treat one patient at a time. * The doctor as a list of competencies (of type String) that * indicates what conditions s/he can treat. */ public class Doctor { /** * Initialise this doctor with a set of competencies. * @param competencies */ public Doctor(...) { // 2b // Implementation hidden } /** * Indicates to what extent this doctor can treat the provided patient. * The value is the number of the patient's conditions this doctor can * treat divided by the number of conditions the patient has. * Conditions and competences are matched using simple String comparison. * @param patient * @return the ratio of the patient's conditions that this doctor * can treat. */ public double canTreat(final Patient patient) { // 2b // Implementation hidden } /** * "Treats" the patient by removing all the patient's conditions * that this doctor can treat. */ public void treat() { // 2b // Implementation hidden } /** * @return the patient this doctor is treating, or null if s/he * isn't currently treating any patient. */ public Patient getPatient() { // Implementation hidden } /** * @return true if this doctor is currently treating a patient, * otherwise false. */ public boolean isAvailable() { // Implementation hidden } /** * Sets the patient that this doctor is treating, use null to * indicate s/he isn't currently treating any patient. * @param patient */ public void setPatient(final Patient patient) { // Implementation hidden } } /** * A class for managing a set of doctors and the patients they're treating. * When doctors or patients arrive, it is made sure that patients are treated * as soon as possible. */ public class TreatmentUnit { // Internal declaration hidden /** * Adds a doctor and makes sure s/he starts treating a patient, if one * is waiting. * @param doctor */ public void addDoctor(final Doctor doctor) { // Possible changes } /** * @return the currently available doctors */ public Collection<Doctor> getAvailableDoctors() { // Possible changes } /** * Adds a patient to this treatment unit, and makes sure treatment starts * if any doctor is available. * Otherwise the patient is queued for treatment when a doctor becomes * available. * @param patient */ public void addPatient(final Patient patient) { // Possible changes } /** * @param pred the predicate that the doctor must satisfy * @return some doctor satisfying the predicate */ public Doctor getDoctor(final Predicate<Doctor> pred) { // Possible changes } /** * Find the doctor, if any, that treats the provided patient. * @param patient * @return the doctor treating the provided patient, or null if the * patient isn't currently being treated. */ public Doctor getDoctor(final Patient patient) { // Possible changes } /** * Find all patients that are not currently being treated * @return the patients not currently being treated */ public Collection<Patient> getWaitingPatients() { // Possible changes } /** * Finds a waiting patient and sets him/her as the provided doctor's patient. * Will only accept a patient that has some condition that the doctor actually * can treat. * @param doctor the doctor for which a patient to treat should be found * @return true if a patient for the provided doctor was found, false * otherwise. */ private boolean startTreatment(final Doctor doctor) { // Implementation hidden } /** * Finds an available doctor for the provided patient, and sets that doctor to * treat the patient. * Will only accept a doctor that actually can treat some condition for the * provided patient. * @param patient the patient for which a treating doctor should be found * @return true if a doctor for the provided patient was found, false * otherwise. */ private boolean startTreatment(final Patient patient) { // Implementation hidden } /** * Removes the link between doctor and patient, after treatment is finished. * If the patient is fully treated, s/he is removed from this treatment unit, * otherwise another round of treatment is initiated. * Also ensure the doctor starts treating another patient. * @param doctor the doctor that has finished treating his/her patient */ public void treatmentFinished(final Doctor doctor) { // Implementation hidden } }
TreatmentUnit: Løsningsforslaget viser utvidelse med en abstrakt klasse AbstractTreatmentUnit. Denne klassen implementerer alle metoder som ikke involverte regelsett for hvordan en skulle start behandling, og deklarerer de relevante abstrakte metoden (startTreatment *2). Disse ble så implementert i TreatmentUnit, med løsning akkurat som i del 2. En slik abstrakt klasse er ikke påkrevd, men en god idé, fordi den tydeliggjør hva som er ment å variere (polymorfi) i subklassene.
PriorityTreatmentUnit: Her skulle en velge ut den ventende pasienten som hadde høyest prioritet (getPriority). Selv om getPriority ikke ble oppgitt kunne en gjøre antakelser om bruk. Den eneste metoden som måtte implementeres i denne klassen var startTreatment(Doctor), siden prioritering må gjøres hver gang det oppstår en situasjon der pasientene konkurrerer om en doktor som er ankommet eller blitt ledig. Forskjellen fra tidligere implementasjon er at en må gå igjennom alle pasienter (som denne doktoren kan behandle) og lagre den pasienten som til nå har høyest prioritet. Vi har også vist pseudokode som vil være en fullgod besvarelse for denne klassen.
EmergencyPriorityTreatmentUnit: Den eneste metoden som trenger ny logikk er startTreatment(Patient). Derfor er det lurt (men strengt tatt ikke nødvendig) å arve fra PriorityTreatmentUnit, siden vi da kan utnytte den eksisterende prioriteringslogikken. Det er jo bare når en ikke finner en doktor med super.startTreatment(patient) at ny logikk for å avbryte pågående behandling er aktuell. Hvis dette er tilfelle, må en gå igjennom alle doktorer (trenger ny hjelpemetode getAllDoctors-metode) og sjekke om denne doktoren kan behandle pasienten. Merk at vi utelater alle ledige doktorer, siden disse allerede er sjekket i kallet til superklassens metode (dette kan forsåvidt skje i den nye hjelpemetoden, som da kanskje bør hete getAllTreatingDoctors). Vi bruker samme logikk som tidligere for å velge ut den lavest prioriterte pasienten. Vi oppdatere dennes nåværende doktor sin pasient til den nye pasienten (getDoctor(patientToSuspend).setPatient(patient)). Til slutt må pasienten som fikk avbrutt behandlingen få en ny sjanse til behandling av en annen lege med et nytt kall til startTreatment. Det er ganske mange ting å huske på, men det viktigste er tross alt god bruk av arv.
Også her tillates pseudokode, se eksempel i LF. Merk at oppgaven beskrev klassen PriorityTreatment, mens LF kaller den PriorityTreatmentUnit. Det er selvsagt ikke viktig.
public class PriorityTreatmentUnit extends TreatmentUnit { protected double getPriority(Patient patient) { return 0.0; } @Override protected boolean startTreatment(Doctor doctor) { Patient patient = null; for (Patient patient2 : getWaitingPatients()) { if (doctor.canTreat(patient2) > 0.0 && (patient == null || getPriority(patient2) > getPriority(patient))) { patient = patient2; } } if (patient != null) { doctor.setPatient(patient); return true; } return false; } } /* PSEUDOKODE-eksempel FOR startTreatment(doctor) * Går gjennom alle pasienter som venter (getWaitingPatients()) og * holder rede på den med høyest prioritet (getPriority). * Sjekker om det finnes en doktor * med riktig kompetanse (canTreat) * Hvis man har funnet en pasient som kan behandles, * så får doktoren denne (setPatient) og true returneres. */ public class EmergencyPriorityTreatmentUnit extends PriorityTreatmentUnit { @Override public boolean startTreatment(Patient patient) { if (! super.startTreatment(patient)) { Patient patientToSuspend = null; for (Doctor doctor : getAllDoctors()) { if (doctor.canTreat(patient) > 0.0) { Patient existingPatient = doctor.getPatient(); if (existingPatient != null && getPriority(existingPatient) < getPriority(patient) && (patientToSuspend == null || getPriority(existingPatient) < getPriority(patientToSuspend))) { patientToSuspend = existingPatient; } } } if (patientToSuspend != null) { getDoctor(patientToSuspend).setPatient(patient); startTreatment(patientToSuspend); return true; } } return false; } }
Vanlige feil/alternative løsninger:
- Selve forståelsen, beskrivelsen og bruken av arv var vanskelig for en god del.
- Noen forstod prioritet som noe måtte legges inn som en ny samling, eller definerte egne eksempler på hva slags tilstander som ga hvilken prioritet. Poenget her var at en skulle bruke getPriority på en pasient for å få ut et tall en kunne sammenlikne med.
- TreatmentUnit: Det var ikke nødvendig å gjøre denne abstrakt, men en måtte forklare at den ville kunne virke som tidligere.
- PriorityTreatmentUnit: Det viktige var at en måtte arve TU, og gitt vår implementasjon måtte en bare endre på startTreatment(doctor) og gå igjennom getWaitingPatients(). En vanlig feil var å ikke gå igjennom alle pasienter for å finne den som kunne behandles nå og som hadde høyest prioritet, men bare returnere når en har funnet en mulig kobling (i lf vist ved oppdatering av patientToSuspend).
- EmergencyPriorityTreatmentUnit: Denne skulle i følge oppgaveteksten ha den samme funksjonaliteten som PriorityTreatmentUnit, men utvide denne. Noen valgte å implementere startTreatment(doctor) til å være lik som i PTU i stedet for å arve denne. Mange glemte at en måtte sjekke om en pasient kunne behandles med vanlig prioritering før en gikk igjennom doktorer som holdt på med behandling. Mange glemte at når en satt en behandlende doktor til å behandle en pasient, da måtte en håndtere pasienten som suspenderes. Denne må legges inn i pasientlisten igjen (hvis en valgte en løsning der pasienter under behandling ikke var i pasientsamlingen) samt at denne pasienten må må en ny startTreatment(patientToSuspend)
- Ved alternative løsninger tidligere vil riktig løsning være ulik. Dette gjelder for eksempel når en har endt opp med å gjøre ting som egentlig skulle være i startTreatment i addPatient og addDoctor. Disse løsningene blir da vurdert med bakgrunn i tidligere valg/misforståelser, for å i størt mulig grad unngå følgefeil.
- Kode/pseudokode blir gitt poeng avhengig av korrekthet og hvor dypt den går. Overfor finner dere et eksempel på pseudokode for PTU, denne skulle gi full uttelling.
Mens du i del 3 brukte arv, skal du i denne delen bruke delegering for å oppnå det samme. I henhold til delegeringsteknikken definerer vi et grensesnitt, DoctorAllocator. I tillegg lager du (minst) tre hjelpeklasser tilsvarende de tre logikkene beskrevet i del 3, som implementerer grensesnittet. Forklar hvordan koden i vedlagte TreatmentUnit-skjelett skal gjøres fullstendig slik at startTreatment-metoden bruker delegering riktig. Forklar også med tekst og/eller kode hvordan du vil utforme hjelpeklassene som implementerer DoctorAllocator.
Da målet er å vise kunnskap om delegering kan dere bruke pseudokode i denne oppgaven. Det er greit å referere (pseudo)koden i del 3, hvis det er til hjelp.
public interface DoctorAllocator { /** * Finds the patient in the TreatmentUnit that the provided doctor * should start treating. * Note: The method should be free of side effects, i.e. should not modify any object. * @param doctor * @param treatmentUnit * @return the patient that the doctor should treat, or null. */ public Patient allocatePatient(Doctor doctor, TreatmentUnit treatmentUnit); /** * Finds the doctor in the TreatmentUnit that the provided patient * should be treated by. * Note: The method should be free of side effects, i.e. should not modify any object. * @param patient * @param treatmentUnit * @return The doctor that should treat the provided patient, or * null if none was found. */ public Doctor allocateDoctor(Patient patient, TreatmentUnit treatmentUnit); } /** * A class for managing a set of doctors and the patients they're treating. * When doctors or patients arrive, or a doctor finishes a treatment, * it is made sure that patients are treated as soon as possible. */ public class TreatmentUnit { // Implementation of local variables hidden public void addDoctor(final Doctor doctor) { // Implementation hidden } public Collection<Doctor> getAllDoctors() { // Implementation hidden } public Collection<Doctor> getAvailableDoctors() { // Implementation hidden } public void addPatient(final Patient patient) { // Implementation hidden } public Doctor getDoctor(final Predicate<Doctor> pred) { // Implementation hidden } public Doctor getDoctor(final Patient patient) { // Implementation hidden } public Collection<Patient> getAllPatients() { // Implementation hidden } public Collection<Patient> getWaitingPatients() { // Implementation hidden } ... declarations related to the use of DoctorAllocator ... // Part 4 protected boolean startTreatment(final Doctor doctor) { // Part 4 final Patient patient = ... use of DoctorAllocator here ... ... more code here ... } protected boolean startTreatment(final Patient patient) { // Part 4 final Doctor doctor = ... use of DoctorAllocator here ... ... more code here ... } public void treatmentFinished(final Doctor doctor) { // Implementation hidden } }
LF lager løsningen rundt det oppgitte grensesnittet DoctorAllocator, som har to metoder allocateDoctor og allocatePatient. Dette grensesnittet implementeres av en DefaultDoctorAllocator (noe som tilsvarer slik det gjøres i en vanlig TreatmentUnit, altså ikke noe bruk av prioritering). De andre to proriteringsstrategiene implementeres av PriorityDoctorAllocator og EmergencyPriorityDoctorAllocator. LF bruker arv for begge disse, tilsvarende løsningen i del 3, men det er ikke viktig fordi temaet her er delegering. Logikken som implementeres er omtrent som i del 3, med en vesentlig forskjell: Grensesnitt-metodene skal bare finne og returnere riktig doktor/pasient (koblingen som skal etableres), ikke endre noe, altså kalles ikke setPatient eller startTreatment.
DefaultDoctorAllocator: Implementerer ingen prioritet og tilsvarer et vanlig TreatmentUnit-valg. Hvis doktor-pasient-kobling kan etableres returneres doktor eller pasient, ellers null.
PriorityDoctorAllocator: Arver fra DefaultDoctorAllocator, så en slipper unna med redefinering av allocatePatient, med logikk tilsvarende PriorityTreatmentUnit.
EmergencyPriorityDoctorAllocator: Arver fra PriorityDoctorAllocator, så en slipper unna med redefinering av allocateDoctor, med logikk tilsvarende EmergencyPriorityTreatmentUnit.
TreatmentUnit: Denne må endres så den 1) bruker delegering ved å kalle en DoctorAllocator-implementasjon og 2) håndterer faktisk endring av doktor-pasient-koblingen, iht. resultatet fra DoctorAllocator-implementasjonen. Delegering rigges ved å opprette en doctorAllocator-variabel, her er DefaultDoctorAllocator valgt, men den kan byttes ut med et kall til setDoctorAllocator. Begge startTreatment-metodene må endres. For begge består endringen av at en må kalle den tilsvarende allocate-metoden og sjekke om resultatet er null før en knytter relasjonen. Merk at en må ta høyde for at behandling kan avbrytes.
public interface DoctorAllocator { /** // Long description at git */ public Patient allocatePatient(Doctor doctor, TreatmentUnit treatmentUnit); /** // Long description at git */ public Doctor allocateDoctor(Patient patient, TreatmentUnit treatmentUnit); } public class DefaultDoctorAllocator implements DoctorAllocator { @Override public Patient allocatePatient(final Doctor doctor, final TreatmentUnit treatmentUnit) { for (final Patient patient : treatmentUnit.getWaitingPatients()) { if (doctor.canTreat(patient) > 0.0) { return patient; } } return null; } @Override public Doctor allocateDoctor(final Patient patient, final TreatmentUnit treatmentUnit) { for (final Doctor doctor : treatmentUnit.getAvailableDoctors()) { if (doctor.canTreat(patient) > 0.0) { return doctor; } } return null; } } public class PriorityDoctorAllocator extends DefaultDoctorAllocator { /** * Dummy class */ protected double getPriority(final Patient patient) { return 0.0; } @Override public Patient allocatePatient(final Doctor doctor, final TreatmentUnit treatmentUnit) { Patient patient = null; for (final Patient patient2 : treatmentUnit.getWaitingPatients()) { if (doctor.canTreat(patient2) > 0.0 && (patient == null || getPriority(patient2) > getPriority(patient))) { patient = patient2; } } return patient; } } public class EmergencyPriorityDoctorAllocator extends PriorityDoctorAllocator { @Override public Doctor allocateDoctor(final Patient patient, final TreatmentUnit treatmentUnit) { final Doctor doctor = super.allocateDoctor(patient, treatmentUnit); if (doctor != null) { return doctor; } Patient patientToSuspend = null; for (final Doctor doctor2 : treatmentUnit.getAllDoctors()) { if (doctor2.canTreat(patient) > 0.0) { final Patient existingPatient = doctor2.getPatient(); if (existingPatient != null && getPriority(existingPatient) < getPriority(patient) && (patientToSuspend == null || getPriority(existingPatient) < getPriority(patientToSuspend))) { patientToSuspend = existingPatient; } } } if (patientToSuspend != null) { return treatmentUnit.getDoctor(patientToSuspend); } return null; } } public abstract class TreatmentUnit { // Diverse kode utelatt fra tidligere private DoctorAllocator doctorAllocator = new DefaultDoctorAllocator(); public void setDoctorAllocator(final DoctorAllocator doctorAllocator) { this.doctorAllocator = doctorAllocator; } protected boolean startTreatment(final Doctor doctor) { final Patient patient = doctorAllocator.allocatePatient(doctor, this); if (patient != null) { doctor.setPatient(patient); return true; } return false; } protected boolean startTreatment(final Patient patient) { final Doctor doctor = doctorAllocator.allocateDoctor(patient, this); if (patient != null) { final Patient oldPatient = doctor.getPatient(); doctor.setPatient(patient); if (oldPatient != null) { startTreatment(oldPatient); } return true; } return false; } public void treatmentFinished(final Doctor doctor) { if (doctor.getPatient() == null) { throw new IllegalStateException(doctor + " has no patient!"); } final Patient patient = doctor.getPatient(); doctor.setPatient(null); if (patient.requiresTreatment()) { startTreatment(patient); } else { patients.remove(patient); } startTreatment(doctor); } }
Vanlige feil/alternative løsninger:
- Svært mange lot TreatmentUnit implementere DoctorAllocator, i ymse varianter. Det var altså meningen at egne klasser skulle implementere hhv TU, PTU og EPTU (gjerne med arv, og lik logikk som i oppgave 3).
- I tillegg var det viktig at en Allocator skulle returnere et doktor eller pasientobjekt, og altså ikke sette i gang behandling og slikt. Dette skulle gjøres i TreatmentUnit, som vist i LF.
- Som i eksempelet over var noe av det viktigste å vise forståelse for hvordan delegering fungerer. Det betyr at du setter bort valget til en egen klasse som implementerer DoctorAllocator, og så bruker du i din TreatmentUnit tre ulike slike alt etter type TreatmentUnit. Det er altså ikke tre ulike typer TU vi er på jakt etter, men i stedet tre typer Allocators. En implementasjon med tre typer TreatmentUnit som implementerer DoctorAllocator er ikke delegering.
- TU må i stedet endres til å inkludere en slik allocator, og endringer som beskrevet i LF og over LF.
Vi gjentar fra del 3 hvordan vi ønsker å knytte doktor til pasient, men dette skal nå gjøres ved å implementere DoctorAllocator i stedet for å bruke arv:
- Pasienter knyttes til den første (og ikke nødvendigvis beste) doktoren som kan behandle en eller flere tilstander hos pasienten.
- Pasientene kan ha tilstander med varierende kritikalitet, som gjør at de prioriteres foran resten av køen når en doktor er ledig. Du kan forutsette at det finnes en getPriority-metode i Patient, og at denne prioriteten settes ved innlegging av pasientdata. Per, som har en mer alvorlig skade enn Jens, vil altså behandles raskere selv om han kommer inn senere. (Såfremt Jens ikke allerede har startet behandlingen.)
- Pågående pasientbehandling (selv om den er prioritert) skal kunne avbrytes og doktoren skal starte behandling av en ny pasient. Dette skal skje dersom det kommer inn en ny pasient, og dennes prioritet er høyere enn hos pågående. Du kan også her forutsette at den finnes en getPriority-metode i Patient. Anta at Jens er under behandling hos doktor Who. Ida kommer har en tilstand med høyere prioritet enn Jens, og kan kun behandles av Who. Jens sin behandling blir derfor satt på vent, og den samme doktoren begynne behandling av Ida.
På venterommet skal det være et informasjonspanel, som forteller ventende pasienter om hvilken doktor de skal gå til, når det er deres tur. Panelet implementeres med JavaFX, og tanken er at kontroller-objektet (av typen TreatmentUnitController ) skal lytte på et TreatmentUnit-objekt og få beskjed når koblingen mellom doktor og pasient etableres, slik at panelet kan vise en tekst av typen «Pasient X skal gå til Doctor Y» (du kan forvente at Patient og Doktor har en toString() som gir navn, dette trenger du ikke implementere i tidligere oppgaver).
I denne delen kan du bygge videre på koden fra del 2. Du trenger altså ikke ha løst del 3 eller 4.
// TreatmentUnit.fxml:<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.text.Font?> <HBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="ord2019.part5.TreatmentUnitController"> <Label fx:id="patientMessage" text="<Her kommer meldinger til pasienter>"> </Label> </HBox> // TreatmentUnitController.java: public class TreatmentUnitController { TreatmentUnit treatmentUnit; public TreatmentUnitController() { treatmentUnit = // assume treatmentUnit is set automatically. // ... 5 b) other initialization ... } // ... 5 b) declarations and methods here... }
Oppgave 5a) – Lyttergrensesnitt
Definer et egnet lyttergrensesnitt og forklar med tekst og kode hvordan TreatmentUnit må endres for å kunne fungere som observert i et observatør-observert-forhold.
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 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 den observerte, TreatmentUnit, er at alle lytterne lagres i en private Collection 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)).
public interface TreatmentListener { public void treatmentStarted(Doctor doctor, Patient patient, TreatmentUnit treatmentUnit); } public class TreatmentUnit { // Lots of code unchanged here, find it in part 2 of TU. private Collection<TreatmentListener> treatmentListeners = new ArrayList<>(); public void addTreatmentListener(TreatmentListener listener) { treatmentListeners.add(listener); } public void removeTreatmentListener(TreatmentListener listener) { treatmentListeners.remove(listener); } private void fireTreatmentStarted(Doctor doctor, Patient patient) { for (TreatmentListener treatmentListener : treatmentListeners) { treatmentListener.treatmentStarted(doctor, patient, this); } } private boolean startTreatment(Doctor doctor) { for (Patient patient : getWaitingPatients()) { if (doctor.canTreat(patient) > 0.0) { doctor.setPatient(patient); fireTreatmentStarted(doctor, patient); return true; } } return false; } private boolean startTreatment(Patient patient) { for (Doctor doctor : getAvailableDoctors()) { if (doctor.canTreat(patient) > 0.0) { doctor.setPatient(patient); fireTreatmentStarted(doctor, patient); return true; } } return false; } }
Vanlige feil/alternative løsninger:
- En god løsning skal definere både grensesnittet som lytterne (dermed TreatmentUnitController ) implementerer.
- Dette grensesnittet må inkludere en metode som kalles når noe skjer (se under). Denne metoden må inneholde nok informasjon til at Labelobjektet kan oppdateres med pasient- og doktorinfo. Siden doktoren vet hvilken pasient hen har fått tildelt, så trenger en egentlig bare sende med det relevante doktorobjektet.
- Mange bommet på at en må definere en metode i TU (fireTreatmentStarted) som en så går inn og kaller på alle relevante steder (hver gang en behandling skal starte, med andre ord startTreatment(Patient) og startTreatment(Patient).
- En måtte huske på å lage en samling av lyttere, lage gettere/settere /eller i alle fall indikere det.
- I fireTreatmentStarted måtte en huske på å gå igjennom alle lytterne.
Oppgave 5b) – Controller
Fyll ut kodeskjelettet for kontrollerklassen TreatmentUnitController, slik at det fungerer med den vedlagte FXML-koden og fyller rollen som observatør av TreatmentUnit. Som indikert i kodeskjelettet, så kan du anta at treatmentUnit-variablen i kontrollerklassen settes «automagisk» i konstruktøren.
I FXML-koden ser en at det finnes en Label med fx:id="patientMessage". Her må man skjønne/gjette at det er dette objektet som skal oppdateres når en pasient skal gå til en doktor. En kan også lese at controlleren er en (instans av) TreatmentUnitController.
TreatmentUnitController:
- Label patientMessage i FXML må knyttes til en variabel i TreatmentUnitController, gjennom å bruke @FXML-annotasjonen og Label-type.
- Konstruktøren oppretter en ny TreatmentUnit. Så er det litt valgfritt hva som skjer, men den viktige biten er at man i konstruktøren også må opprette forbindelsen mellom TreatmentUnitController som lytter/observatør og TreatmentUnit som observert vha. et kall til addTreatmentListener-metoden fra 5a.
- TreatmentUnitController må implementere lyttergrensesnittet og (den ene) lyttermetoden må oppdaterer status på Label-objektet.
public class TreatmentUnitController implements TreatmentListener { private TreatmentUnit treatmentUnit; public TreatmentUnitController() { treatmentUnit = new TreatmentUnit(); // ... treatmentUnit.addTreatmentListener(this); } @FXML private Label patientMessage; @Override public void treatmentStarted(Doctor doctor, Patient patient, TreatmentUnit treatmentUnit) { patientMessage.setText(patient.getName() + " bes gå til doktor " + doctor.getName()); } }
Vanlige feil/alternative løsninger:
- Etter at TreatmentUnit ble definert inni kons.truktøren til TUC måtte en kalle TU sin metode som ble laget i 5a, addTreatmentListener(this).
- this som argument i forrige punkt var viktig.
- @FXML (huske denne) Label måtte hete akkurat det samme som i FXML-filen som ble lagt ved.
- Label over måtte deklareres inni hovedkoden til TUC, ikke inni konstruktøren.
- Metoden som skulle kalles når lytteren (this) kalles (fra 5a) heter i LF treatmentStarted. Her skrev mange ut informasjon om pasienter og doktorer uten å ha noen objekter å referere til.