Hint til oppgave 8
Dette er en omfattende oppgave, og det lønner seg å bygge den opp i flere steg.
Man må lage klasser for å representere datastrukturen (objektsamlinger), og også klasser for brukergrensesnittet.
Brukergrensesnittet
Brukeren skal kunne registrere data om både varer, kunder og bestillinger. Det kan f.eks. gjøres i tre vinduer, slik:
Tanken er at programmet leser inn data fra fil(er) ved oppstart, og viser opplysninger om ett objekt av gangen (én vare, én kunde, én bestilling). Brukeren kan navigere i objektsamlingene med knapper Forrige og Neste, samt legge til (Ny) og slette (Slett).
Ved redigering av tekstfeltene kunne man valgt å lytte til samtlige tekstfelt, og oppdatert objektsamlingene "automatisk". Her er det lagt opp til en litt enklere variant, med en egen knapp Oppdater. NB! I løsningsforslaget vil Oppdater ha effekt på objektsamlingene i hurtigminnet, men ikke skrive opplysningene til fil.
Plassering av tekstfelt med tilhørende etiketter kan f.eks. gjøres med GridLayout. Hvis hele vinduet er en JFrame med BorderLayout, kan panelet som skal inneholde etiketter og tekstfelt opprettes slik for kundeskjemaet:
JPanel pnlSkjema = new JPanel(); // 3 rader og 2 kolonner med 5 piksler luft horisontalt og vertikalt GridLayout gridLayout = new GridLayout(3, 2, 5, 5); pnlSkjema.setLayout(gridLayout); add(pnlSkjema, BorderLayout.CENTER); // Legg deretter knappene i et eget panel, som plasseres i sør.
Merk at varenr, kundenr og bestillingsnr er "grået ut". I løsningsforslaget sørger programmet for å "autogenerere" disse løpenummerne, og brukeren får dermed ikke lov til å skrive inn noe i disse tekstfeltene.
Skjemaet for Bestillinger byr på en ekstra utfordring. I en fullgod løsning bør man tillate vilkårlig mange varer på en og samme bestilling. Løsningsforslaget forenkler litt, og begrenser dette til 5 varer pr. bestilling. Dermed kan man bygge opp vinduet på samme måte for alle bestillinger.
De tre skjemaene kan programmeres som subklasser av JFrame på vanlig måte, men bør ikke avslutte programkjøring når brukeren lukker vinduet. I stedet kan man lage en egen "hovedmeny" som vises ved oppstart:
Under menyen Fil kan man legge et valg for å skrive (oppdaterte) objektsamlinger til fil. Husk at lagring også skal gjøres når brukeren avslutter programmet ved å lukke hovedvinduet.
Knappene Varer, Kunder og Bestillinger kan sørge for å gjøre de ulike vinduene synlige:
vareGui.setVisible(true);
Datamodellen
Hvis man skulle lagret data om varer, kunder og bestillinger i en database, ville følgende tabellstruktur vært en mulig løsning:
- Vare(Nr, Navn, Pris)
- Kunde(Nr, Navn, Adresse)
- Bestilling(Nr, Dato, KundeNr)
- BestillingsLinje(BestNr, VareNr, Antall)
Varer, kunder og bestillinger er identifisert av et unikt løpenr, som er gitt navnet Nr i alle tre tabeller. Hver bestilling er knyttet til en kunde (KundeNr), og kan ha et antall bestillingslinjer, som hver inneholder et varenr og et antall.
Kommentar: I databaseterminologi vil Nr være primærnøkkel i Vare, Kunde og Bestilling, BestNr+VareNr vil være primærnøkkel i Bestillingslinje. Et databasesystem vil sjekke at det f.eks. ikke blir registrert to varer med samme nummer. Dessuten vil KundeNr i Bestilling være fremmednøkkel mot Kunde, og VareNr vil være fremmednøkkel mot Vare. Databasesystemet vil sjekke at kunder og varer som blir referert fra en bestilling faktisk eksisterer i tabellene Vare og Kunde.
Lagring på fil
En grei løsning er å lagre data på kommaseparerte tekstfiler. Man kan velge å bruke fire filer med samme oppbygging som databasetabellene skissert over, men i løsningsforslaget er det valgt å lagre Bestilling og BestillingsLinje i én fil.
vare.dat:
1;Spade;220.50; 2;Hakke;199.00; 3;Spett;170.00;
kunde.dat:
1;Per Hansen;Kongens gate 3; 2;Lise Mo;Bjørkelia 17;
bestilling.dat:
1;20.03.2013;1;2;6; 2;20.03.2013;2;1;1;3;2;
Andre linje sier at bestilling nr 2 er registrert 20.03.2013 og tilhører kunde nr. 2. Til denne bestillingen hører det to bestillingslinjer, 1 enhet av vare nr. 1, og 2 enheter av vare nr. 3.
Objektsamlinger
Start med håndtering av varer (eller kunder). Lag to klasser:
- Vare.java
- VareSamling.java
Klassen Vare kan bygges opp på vanlig måte, med konstruktører og set/get-metoder. Lag gjerne en konstruktør som bygger opp Vare-objektet fra en String, der tekststrengen er slik varen blir lagret på fil, og en tilsvarende metode som gjør om et Vare-objekt til en tekststreng for lagring til fil.
Klassen VareSamling skal være "modellen" bak skjemaet for registrering av varedata. Den må opplagt kunne ta vare på et antall varer. Man kan f.eks. bruke ArrayList til dette.
Fordi skjemaet kun viser én vare av gangen, er det behagelig om VareSamling også tar vare på hva som nå er "aktiv" vare. Det kan gjøres med en heltallsvariabel pos.
For håndtering av autonummerering kan man innføre nok en heltallsvariabel nesteNr.
Forslag til objektvariabler og metoder i VareSamling:
public class VareSamling { private ArrayList<Vare> liste; // Varesamlingen private int pos; // Aktiv vare private int nesteNr; // Neste løpenr // Sett inn en ny vare med et friskt løpenr public void ny() { ... } // Slett vare med gitt nr public boolean slett(int nr) { ... } // Returner aktiv vare public Vare denne() { ... } // Gå til forrige vare public void forrige() { ... } // Gå til neste vare public void neste() { ... } // Les inn varesamlingen fra fil public void lesFraFil() { ... } // Skriv (oppdatert) varesamling til fil public void skrivTilFil() { ... } }
Lag deretter en tilsvarende løsning for kunder, og til slutt bestillinger. Denne siste er mer krevende, fordi den må inneholde en liste med "bestillingslinjer". Lag en hjelpeklasse BestillingsLinje med objektvariabler for å ta vare på varen og antallet. Klassen Bestilling kan deretter gis en objektvariabel liste (i tillegg til løpenr, dato og kunde):
private ArrayList<BestillingsLinje> liste;
Bruk av subklasser
De tre objektsamlingene har noen fellestrekk:
- Både varer, kunder og bestillinger har et autogenerert løpenummer.
- Alle tre objektsamlinger skal fungere som "modell" bak et brukergrensesnitt som tilbyr knapper for å gå til forrige, gå til neste, opprette ny, oppdatere og slette.
- Alle tre objektsamlinger skal kunne leses inn fra og skrives til kommaseparerte tekstfiler.
Dette antyder en løsning med subklasser.
I løsningsforslaget er det valgt å opprette en superklasse BrimiObjekt over Vare, Kunde og Bestilling. Her blir selve løpenummeret lagret. Dessuten er det opprettet en objektsamling BrimiSamling, som kan inneholde enten Vare-objekt, Kunde-objekt eller Bestilling-objekt.
Prøv gjerne å bygge om en første løsning på denne måten, der du altså vil oppnå å erstatte de tre klassene VareSamling, KundeSamling og BestillingSamling med en mer generell løsning BrimiSamling.
Også de tre vinduene har en god del fellestrekk, som fører til gjentak av nesten identisk kode. Prøv gjerne å lage en felles superklasse for vinduene. Den kan bla. ta seg av knapperaden.
Mangler i løsningsforslaget
Det er sikkert flere ting, men her er i hvert fall to opplagte forbedringer:
- Lovlighetskontroll på inndata: Programmet bør sjekke at varepriser er desimaltall med to desimaler, at bestillingsdatoer er lovlig bygd opp, og at varenr/kundenr er heltall.
- Konsistenskontroll: Det bør også sjekkes at kundenr/varenr som tastes inn på en bestilling tilhører en lovlig kunde/vare. Merk at et tilsvarende problem oppstår hvis brukeren prøver å slette en vare/kunde, som det finnes bestillinger som "peker på". I en database kontrolleres slike situasjoner med såkalte fremmednøkler.