-
Notifications
You must be signed in to change notification settings - Fork 1
Viikon 6 paikanpaalla tehtavat
Kumpula Biershopin ohjelmointi on käynnissä. Esimies antaa tehtäväksesi ohjelmoida luokan Ostoskori.
Kuten viikon kolme esimerkkivastauksesta selviää, Ostoskori ei talleta suoraan luokan Tuote-olioita, ostoskoriin vietyjä ostoksia vastaavat Ostos-luokan oliot:
Luokka Tuote on hyvin suoraviivainen. Tuotteesta tiedetään nimi, hinta ja varastosaldo:
public class Tuote {
private String nimi;
private int hinta;
private int saldo;
public Tuote(String nimi, int hinta) {
this.nimi = nimi;
this.hinta = hinta;
}
public void setHinta(int hinta) {
this.hinta = hinta;
}
public int getHinta() {
return hinta;
}
public String getNimi() {
return nimi;
}
public int getSaldo() {
return saldo;
}
public void setSaldo(int saldo) {
this.saldo = saldo;
}
@Override
public String toString() {
return nimi + " " + hinta + " euroa";
}
}
Tuote siis kuvaa yhden tuotteen esim. Lapin kulta tiedot (nimi, hinta ja varastosaldo). Ostoskoriin ei laiteta tuotteita vaan Ostoksia, ostos viittaa tuotteeseen ja kertoo kuinka monesta tuotteesta on kysymys. Eli jos ostetaan esim. 24 Lapin kultaa, tulee ostoskoriin Ostos-olio joka viittaa Lapin kulta -tuoteolioon sekä kertoo että tuotetta on valittu 24 kpl. Ostos-luokan koodi:
public class Ostos {
private int lkm;
private Tuote tuote;
public Ostos(Tuote tuote) {
this.lkm = 1;
this.tuote = tuote;
}
public int hinta() {
return lkm * tuote.getHinta();
}
public int lukumaara() {
return lkm;
}
public String tuotteenNimi() {
return tuote.getNimi();
}
public void muutaLukumaaraa(int muutos) {
lkm += muutos;
if ( lkm<0 ) {
lkm = 0;
}
}
@Override
public int hashCode() {
return this.tuote.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Ostos other = (Ostos) obj;
return this.tuote.equals(other.tuote);
}
}
Tehtävänäsi on siis ohjelmoida luokka ostoskori. Ostoskorin API:n tulee näyttää seuraavalta (metodeille on lisätty returnit jotta kääntäjä ei valittaisi koodista):
public class Ostoskori {
public int tuotteitaKorissa() {
// kertoo korissa olevien tuotteiden määrän
// metodin nimi on hieman huono, kyseessä oikeastaan koriin lisättyjen "asioiden" määrä
// eli jos koriin lisätty 2 kpl tuotetta "Koff", tulee metodin palauttaa 2
return -1;
}
public int hinta() {
// kertoo korissa olevien tuotteiden yhteenlasketun hinnan
return -1;
}
public void lisaaTuote(Tuote lisattava) {
// lisää tuotteen
}
public void poista(Tuote poistettava) {
// poistaa tuotteen
}
public List<Ostos> ostokset() {
// palauttaa listan jossa on korissa olevat ostokset
return null;
}
public void tyhjenna() {
// tyhjentää korin
}
}
Ohjelmoimme nyt ostoskorin käyttäen Test Driven Development -tekniikkaa, eli kirjoitamme ensin testejä ja vasta sen jälkeen testit toteuttavan koodin.
Huom: tämä tehtävä on samantapainen kun Ohjelmoinnin jatkokurssin viikon 3 ostoskori. Älä kuitenkaan copypastaa tehtävän koodia, sillä nyt tehtävässä ostoskorissa on pieniä mutta ratkaisevia eroja ohjan ostoskoriin.
LUO Maven-muotoinen NetBeans-projekti ja kopioi sinne ylläolevat luokat Tuote ja Ostos ja pohja luokalle Ostoskori.
Tee koodin sisältävästä hakemistosta git-repositorio
Gitignoroi hakemisto target
Tee seuraavat testit ja aina jokaisen testin jälkeen testin läpäisevä koodi
Lisää ja commitoi muutokset jokaisen vaiheen jälkeen, anna kuvaava commit-viesti
1. Luodun ostoskorin hinta ja tuotteiden määrä on 0.
tee siis testi joka testaa ylläolevan. Kun testi on valmis, ohjelmoi ostoskoria sen verran että testi menee läpi
Lisää ja commitoi muutokset ja anna kuvaava commit-viesti.
2. Yhden tuotteen lisäämisen jälkeen ostoskorissa on 1 tuote.
huom: joudut siis luomaan testissäsi tuotteen jonka lisäät koriin:
@Test
public void yhdenTuotteenLisaamisenJalkeenKorissaYksiTuote() {
Tuote karjala = new Tuote("Karjala", 3);
kori.lisaaTuote(karjala);
// ...
}
Täsmennys:
Vaikka metodin lisaaTuote parametrina on Tuote-olio, ostoskori ei tallenna tuotetta vaan luomansa Ostos-olion joka "tietää" mistä tuotteesta on kysymys.
Lisää ja commitoi muutokset ja anna kuvaava commit-viesti.
3. Yhden tuotteen lisäämisen jälkeen ostoskorin hinta on sama kuin tuotteen hinta.
Lisää ja commitoi muutokset.
4. Kahden eri tuotteen lisäämisen jälkeen ostoskorissa on 2 tuotetta
Lisää ja commitoi muutokset.
5. Kahden eri tuotteen lisäämisen jälkeen ostoskorin hinta on sama kun tuotteiden hintojen summa
Lisää ja commitoi muutokset.
6. Kahden saman tuotteen lisäämisen jälkeen ostoskorissa on 2 tuotetta
Lisää ja commitoi muutokset.
7. Kahden saman tuotteen lisäämisen jälkeen ostoskorin hinta on sama kun 2 kertaa tuotteen hinta
Lisää ja commitoi muutokset.
8. Yhden tuotteen lisäämisen jälkeen ostoskori sisältää yhden ostoksen
tässä testataan ostoskorin metodia ostokset():
@Test
public void yhdenTuotteenLisaamisenJalkeenKorissaYksiOstosOlio() {
kori.lisaaTuote(tuote1);
ArrayList<Ostos> ostokset = kori.ostokset();
// testaa että metodin palauttamin listan pituus 1
}
Lisää ja commitoi muutokset.
9. Yhden tuotteen lisäämisen jälkeen ostoskori sisältää ostoksen, jolla sama nimi kuin tuotteella ja lukumäärä 1
Testin on siis tutkittava jälleen korin metodin ostokset palauttamaa listaa:
@Test
public void yhdenTuotteenLisaamisenKorissaYksiOstosOlioJollaOikeaTuotteenNimiJaMaara() {
kori.lisaaTuote(karhu);
Ostos ostos = kori.ostokset().get(0);
// testaa täällä, että palautetun listan ensimmäinen ostos on halutunkaltainen.
}
Lisää ja commitoi muutokset.
10. Kahden eri tuotteen lisäämisen jälkeen ostoskori sisältää kaksi ostosta
Lisää ja commitoi muutokset.
11. Kahden saman tuotteen lisäämisen jälkeen ostoskori sisältää yhden ostoksen
eli jos korissa on jo ostos "karhu" ja koriin lisätään sama tuote uudelleen, tulee tämän jälkeen korissa olla edelleen vain yksi ostos "karhu", lukumäärän tulee kuitenkin kasvaa kahteen:
Lisää ja commitoi muutokset.
12. Kahden saman tuotteen lisäämisen jälkeen ostoskori sisältää ostoksen jolla sama nimi kuin tuotteella ja lukumäärä 2
Lisää ja commitoi muutokset.
13. Jos koriin on lisätty tuote ja sama tuote poistetaan, on kori tämän jälkeen tyhjä
tyhjä kori tarkoittanee että tuotteita ei ole, korin hinta on nolla ja ostoksien listan pituus nolla
Lisää ja commitoi muutokset.
14. jos korissa on kaksi samaa tuotetta ja toinen näistä poistetaan, jää koriin ostos jossa on tuotetta 1 kpl
Lisää ja commitoi muutokset.
15. Metodi tyhjenna tyhjentää korin
Lisää ja commitoi muutokset.
Jos ostoskorissasi on mukana jotain ylimääräistä, refaktoroi koodiasi niin että kaikki turha poistuu. Erityisesti ylimääräisistä oliomuuttujista kannattaa hankkiutua eroon, tarvitset luokalle vain yhden oliomuuttujan, kaikki ylimääräiset tekevät koodista sekavamman ja vaikeammin ylläpidettävän. Jos luokassasi on ylimääräisiä oliomuuttujia, poista ne. Varmista koko ajan testien avulla ettet riko mitään.
Lisää ja commitoi mahdolliset muutokset.
Tarkastele komennolla gitk edellisen tehtävän aikana syntyneitä committeja.
Jokaisella commitilla on yksikäsitteinen tunniste:
Komennolla git diff on mahdollista vertailla kahden eri commitin välillä tapahtuneita muutoksia.
Kopioi jonkin commitin tunniste, ja vertaa nykyisen tilanteen ja commitin eroa antamalla komento
git diff tunniste
:
Committien tunnuksia ei ole välttämätöntä käyttää kokonaisuudessaan. Muutama tunnisteen ensimmäinen merkki riittää.
Esimerkissä komennon git diff 21fccb6820aa0c8cc2388acd73040adb1c0ccca9
sijaan on riittävää antaa komento git diff 21fccb
On myös mahdollista palauttaa tiedostot vanhan commitin tilanteeseen jos esim. haluamme suorittaa testit jollekin ohjelman aiemmalle versiolle. Siirtyminen vanhaan commitiin tapahtuu komennolla git checkout
:
mluukkai@melkki:~/Biershop$ git checkout 21fccb6820aa0c8cc2388acd73040adb1c0ccca9
Note: checking out '21fccb6820aa0c8cc2388acd73040adb1c0ccca9'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b <new-branch-name>
HEAD is now at 21fccb6... tuotteen lisäämisen jälkeen korissa ostos
Eli vaikka teet jotain muutoksia ollessasi vanhassa commitissa, muutokset eivät vaikuta minkään commitin sisältöön.
Mene nyt johonkin vanhaan commitiisi ja katso miltä tiedostot näyttävät.
Palaa takaisin nykytilaan antamalla komento git checkout master
Tee nyt luokkaan Ostoskori jokin muutos ja commitoi se.
Rupeatkin katumaan muutosta ja haluat perua sen. Voit palauttaa muuttuneen tiedoston jonkin aiemman commitin aikaisen tilan komennolla git checkout commitin_tunniste -- tiedoston_nimi
mluukkai@melkki:~/Biershop$ git checkout 96459e -- src/biershop/Ostoskori.java
mluukkai@melkki:~/Biershop$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: src/biershop/Ostoskori.java
Tiedoston Ostoskori.java sisältö on nyt sama kuin edellisessä commitissa. Muuttunut tila on staging-alueella valmiina committoitavaksi.
Palauta nyt tekemäsi muutos ja commitoi se.
Vastaavalla tavalla on mahdollista palauttaa myös vanhojen committien sisältämiä poistettuja tiedostoja.
Viime viikon paikanpäällä tehdyssä tehtävässä teimme kattavat yksikkötestit Matkakortille ja Kassapäätteelle. Voit ottaa tämän tehtävän pohjaksi joko aiemman koodisi hae projektin sisältämä zip-paketti komennolla
wget https://www.cs.helsinki.fi/u/mluukkai/otm2016/Unicafe.zip
Tehtävän yhteydessä oleva Kassapäätteen koodi sisältää paljon copypastea, sekä edullisen että maukkaan lounaan ostosta käteisellä huolehtiva koodihan on oleellisesti sama. Vastaavasti korttioston suhteen.
Refaktoroi koodi siistiksi. Tavoitteena on saada kaikki mahdollinen copypaste pois ja saada koodista näin mahdollisimman helposti ylläpidettävä. Hyvä ylläpidettävyys tarkoittaa sitä, että jos jokun asia muuttuu (esim. ruuan hinta) tai koodista löydetään bugi, tai kassapäätettä halutaan laajentaa uusilla ateriatyypeillä, tulisi muutostarpeen olla mielellään ainioastaan yhdessä kohdassa koodia.
Refaktoroinnissa luokan ulospäinnäkyvän rajapinnan eli julkisten metodien tulisi toimia samaan tapaan kuin ennen refaktorointia, eli toisinsanoen refaktorointi ei saa rikkoa testejä.
Kun refaktoroit koodia, pyri etenemään mahdollisimman pienin ja hallituin askelein, pitäen testit läpimenevänä lähes koko ajan. Refaktoroidessasi voit muuttaa vapaasti luokan sisäistä toteutusta (esim. lisätä metodeja, muuttaa oliomuuttujien tyyppejä, ym.) haluamallasi tavalla.
Projektista kannattaa tehdä github-repositorio ja koodia kannattaa commitoida tasaisin väliajoin.
Vihje: myytyjen edullisten- ja maukkaiden lounaiden määrää ei kannata pitää yksittäisissä muuttujissa, vaan esim. HashMapissa. Myös eri tyyppisten lounaiden hinnan tallentaminen kannattaa hoitaa HashMapin avulla.