Ohjelmistojen mallintaminen, kesä 2011, laskuharjoitus 4

Kaikissa tehtävissä ei ole yhtä oikeaa vastausta -- tärkeää on pohdinta ja yrittäminen. Jos et ole käyttänyt Javaa, voit ohjelmoida tehtäviin vastauksia myös muilla kielillä. Jos ohjelmointitaitosi ovat ruosteessa, harjoittelu on tie onneen. Jos et saa ohjelmiasi toimimaan, voit myös kuvailla sanallisesti niiden toiminnan.

Tarkoitus on, että teet tehtävät etukäteen ennen laskuharjoitustilaisuutta.

  1. Monopoli ks. esim. http://fi.wikipedia.org/wiki/Monopoli_(peli) on varmasti kaikkien tuntema lautapeli. Tee alustava luokkakaavio, joka kuvaa peliä. Tässä vaiheessa kyseessä voisi olla oikeastaan minkä tahansa nopalla pelattavan lautapelin karkean tason luokkakaavio. Oikeassa monopolissahan on rahaa, taloja, hotelleja ym, mutta ei oteta niitä tässä tehtävässä huomioon.

    Pelin kuvaus siinä tarkkuudessa, mikä meitä nyt kiinnostaa, on seuraavassa:

    Monopolia pelataan käyttäen kahta noppaa. Pelaajia on vähintään 2 ja enintään 8. Peliä pelataan pelilaudalla joita on yksi. Pelilauta sisältää 40 ruutua, ja kukin ruutu tietää, mikä on sitä seuraava ruutu pelilaudalla. Kullakin pelaajalla on yksi pelinappula. Pelinappula sijaitsee aina yhdessä ruudussa. (Pelinappula siis etenee ruudusta toiseen pelin kuluessa.).

    Voit olettaa, että luokat ovat pelilauta, ruutu, pelinappula, pelaaja, noppa ja itse kokonaisuutta mallintava monopoliPeli.

    Mallinna ylläoleva kuvaus UML-luokkakaaviona siten, että luokkakaaviosta tulee esille yhteydet ja osallistumisrajoitteet. Kannattanee miettiä luokkakaavio pala kerrallaan, eli mieti yhteyksiä aluksi vain kahden luokan välillä ja kasvata malliasi siitä eteenpäin.


  2. Etsi seuraavasta (ensimmäisistä laskareista tutusta) kuvauksesta luokkakandidaatit käsiteanalyysiä käyttäen, eli kerää tehtäväkuvauksessa esiintyvät substantiivit. Karsi sen jälkeen ylimääräiset kandidaatit, eli attribuutit, synonyymit ja asiaan kuulumattomat.

    Ohjelmoinnin harjoitustyössä opiskelijan on pidettävä kirjaa käyttämistään työtunneista. Aina kun opiskelija tekee tehtäviä harjoitustyöhön liittyen, on hänen kirjatattava ylös käyttämänsä työaika ja tieto millaiseen tehtävään aika käytettiin. Oletetaan että työtehtävät jakautuvat seuraaviin kuuteen luokkaan:


    Tehtävänä on suunnitella ohjelmoinnin harjoitustyön tuntikirjanpitoa tukeva tietojärjestelmä.

    Ohjelmoinnin harjoitustyö tehdään ryhmässä, johon kuuluu useita opiskelijoita ja yksi ohjaaja. Kukin opiskelija tekee työnsä itsenäisesti. Ryhmän ohjaaja luo jokaiselle ryhmäläiselle käyttäjätunnuksen järjestelmään. Käyttäjätunnuksen luonnin yhteydessä opiskelijan tiedot (mm. osoite, aloitusvuosi, pääaine) kopioidaan OODI-järjestelmästä. Harjoitustyön aikana ohjaaja seuraa ryhmäläisten etenemistä järjestelmän avulla. Ohjelmoinnin harjoitustyön vastuuhenkilön vastuulla on luoda kaikille ohjaajille sopivat käyttäjätunnukset järjestelmään sekä listätä järjestelmään tiedot jokaisesta ryhmästä. Ryhmän tiedot (ohjaaja, tapaamisajat, alku- ja loppupäivämäärä) kopioidaan laitoksen kurssikirjanpitojärjestelmästä.


  3. Tee alustava kohdealueen eli määrittelyvaiheen luokkakaavio ylläolevan tehtävän tuntikirjanpitojärjestelmästä.

  4. Tee luokkakaavio tämän dokumentin tehtävien jälkeen olevan "Maidon elämää"-harjoituksen lähdekoodista. Maidon elämää liittyy tehtävään osoitteessa http://www.cs.helsinki.fi/u/avihavai/ekstraa.html, mutta pystyt hyvin tekemään luokkakaavion vaikket olisikaan ohjelmoinut tehtäväsarjaa. Metodien nimien merkitseminen ei ole oleellista.


  5. Luennoilla ja kalvoissa on mainittu 3 oliosuunnittelun periaatetta:


    Keksi kolme esimerkkiä jotka rikkovat jonkun yllämainituista periaatteista. Näytä myös miten korjaisit tilanteen.


  6. Minkä takia ylläolevassa tehtävässä mainitut periaatteet ovat tärkeitä? Saako niitä rikkoa? Jos saa, niin milloin?

  7. Kuvaa sekvenssikaaviossa mitä Maidon elämässä tapahtuu kun seuraava koodi suoritetaan:
     
    1.  Maatila maatila = new Maatila("Esko", new Navetta(new Maitosailio()));
    2.  // voit ajatella edellisen sekvenssikaaviossa seuraavanlaiseksi:
    3.  // Maitosailio maitosailio = new Maitosailio();
    4.  // Navetta navetta = new Navetta(maitosailio);
    5.  // Maatila maatila = new Maatila( navetta );
    6.  
    7.  Lypsyrobotti robo = new Lypsyrobotti();
    8.  maatila.asennaNavettaanLypsyrobootti(robo);
    9.
    10. Lehma lehma1 = new Lehma();
    11. Lehma lehma2 = new Lehma();
    12. Lehma lehma3 = new Lehma();
    13.
    14. maatila.lisaaLehma(lehma1);
    15. maatila.lisaaLehma(lehma2);
    16.
    17. maatila.eleleTunti();
    18. maatila.hoidaLehmat();
    19.
    20. System.out.println(maatila); 

  8. Piirrä oliokaavio edellisen ohjelman tilanteesta rivin 15 suorituksen jälkeen.

  9. Luentokalvoilla ja kalvoissa mainituilla www-sivuilla on lueteltu joitain koodihajuja (code smell), jotka ovat siis kooditasolla näkyviä merkkejä laiskasta ohjelmointityylistä tai huonosta oliosuunnittelusta. Koodihaju yleensä johtaa jossain vaiheessa ongelmiin jos koodia on muutettava.

    Etsi omista ohjelmoinnin perusteiden ja ohjelmoinnin jatkokurssin laskarivastauksistasi ainakin 4 esimerkkiä, joissa esiintyy jotain koodihajuja. Jos et löydä ohjelmoinnin perusteiden tai ohjelmoinnin jatkokurssin laskarivastauksiasi, voit myös keksiä uusia esimerkkejä.


  10. Refaktoroi ainakin kaksi edellisessä kohdassa löytämääsi koodinpätkää hajuttomaksi, eli muuta niitä siten, että ne säilyvät toiminnallisuudeltaan samanlaisina, mutta niiden rakenne muuttuu selkeäksi ja hyviä periaatteita noudattavaksi.

Maidon elämää-ohjelman lähdekoodit:

public interface Eleleva {
    public void eleleTunti();
}

public interface Lypsava {
    public double lypsa();
}

public class Maitosailio {
    private double tilavuus;
    private double saldo;

    public Maitosailio() {
        this(2000);
    }


    public Maitosailio(double tilavuus) {
        this.tilavuus = tilavuus;
    }

    public double getTilavuus() {
        return tilavuus;
    }

    public double getSaldo() {
        return saldo;
    }

    public double paljonkoTilaaJaljella() {
        return tilavuus-saldo;
    }

    public double otaSailiosta(double maara) {
        if ( maara>getSaldo() )
            maara = getSaldo();
        saldo -= maara;

        return maara;
    }

    public void lisaaSailioon(double maara) {
        if ( maara > paljonkoTilaaJaljella() )
            maara = paljonkoTilaaJaljella();
        saldo += maara;
    }

    @Override
    public String toString() {
        return Math.ceil( getSaldo() )+"/"+ Math.ceil( getTilavuus() );
    }
}

public class Lehma implements Lypsava, Eleleva {
    private static String[] NIMIA = new String[]{
        "Anu", "Arpa", "Essi", "Heluna", "Hely",
        "Hento", "Hilke", "Hilsu", "Hymy", "Ilme", "Ilo",
        "Jaana", "Jami", "Jatta", "Laku", "Liekki",
        "Mainikki", "Matti", "Mella", "Mimmi", "Naatti",
        "Nina", "Nyytti", "Papu", "Pullukka", "Pulu",
        "Rima", "Soma", "Sylkki", "Valpu", "Virpi"};

    private String nimi;
    private double utareidenTilavuus;
    private double maitoaValmiina;

    public Lehma(String nimi) {
        this.nimi = nimi;
        utareidenTilavuus = 15+(int)(Math.random()*25);
        maitoaValmiina = 0;
    }

    public Lehma() {
        this( NIMIA[ (int)(Math.random()*NIMIA.length)] );
    }

    public double lypsa() {
        double lypsettyMaara = maitoaValmiina;
        maitoaValmiina = 0;
        return lypsettyMaara;
    }

    public void eleleTunti() {
        double lisaMaito = 0.7+Math.random()*1.3;
        maitoaValmiina += lisaMaito;
        if ( maitoaValmiina>utareidenTilavuus )
            maitoaValmiina = utareidenTilavuus;
    }

    @Override
    public String toString() {
        return nimiTasattuna() +
                pyorista(maitoaValmiina)+"/"+pyorista(utareidenTilavuus);
    }

    private String pyorista(double luku) {
        return String.format("%.1f", luku);
    }

    private String nimiTasattuna() {
        return String.format("%-10s",nimi);
    }
}

public class Lypsyrobotti {
    private Maitosailio sailio;

    public void lypsa(Lypsava lypsettava) {
        if ( sailio==null ) {
            System.out.println("Ei kiinnitetty maitosäiliöön, ei voida lypsää.");
            return;
        }

        double maitoa = lypsettava.lypsa();
        sailio.lisaaSailioon(maitoa);
    }

    public void setMaitosailio(Maitosailio sailio) {
        this.sailio = sailio;
    }

}

public class Navetta {
    private Maitosailio sailio;
    private Lypsyrobotti robootti;

    public Navetta(Maitosailio maitosailio) {
        sailio = maitosailio;
    }

    public void hoida(Lehma ammu) {
        robootti.lypsa(ammu);
    }

    public void asennaLypsyrobotti(Lypsyrobotti robo) {
        this.robootti = robo;;
        robootti.setMaitosailio(sailio);
    }

    public void hoida(List lehmaLista) {
        for ( Lehma lehma : lehmaLista )
            hoida(lehma);
    }

    @Override
    public String toString() {
        return sailio.toString();
    }
}

public class Maatila implements Eleleva {
    private String omistaja;
    private Navetta navetta;
    private List lehmat;

    public Maatila(String omistaja, Navetta navetta) {
        this.omistaja = omistaja;
        this.navetta = navetta;
        this.lehmat = new ArrayList();
    }

    @Override
    public String toString() {
        String tulostus = "Maatilan omistaja: "+omistaja + "\n" +
                          "Navetan maitosäiliö "+navetta.toString() +"\n";
        if ( lehmat.isEmpty() )
            tulostus += "Ei lehmiä.";
        else {
            String lehmajono = "";
            for ( Lehma lehma :lehmat )
                lehmajono += "   "+lehma + "\n";
             lehmajono.substring(0, lehmajono.length()-1);
             tulostus += "\n"+lehmajono;
        }


        return tulostus;
    }

    public void lisaaLehma(Lehma lehma) {
        lehmat.add(lehma);
    }

    public void eleleTunti() {
        for ( Lehma lehma : lehmat )
            lehma.eleleTunti();
    }

    public void hoidaLehmat() {
        navetta.hoida(lehmat);
    }

    public void asennaNavettaanLypsyrobootti(Lypsyrobotti robo) {
        navetta.asennaLypsyrobotti(robo);
    }
}