Oliosuunnittelu ja luokkakohtaiset komponentit
Sisällys Ohjelmistosuunnittelusta Tarvittavien luokkien tunnistaminen Luokka-attribuutit ja -metodit Metodien suunnittelu Jukka Harju, 2006 2
Ohjelmistosuunnittelu Ohjelmistosuunnittelussa käytetään useita eri prosessimalleja (mm. Rational Unified Process ja extreme Programming) Käytettävästä mallista riippumatta ohjelmistoprosessi sisältää lähes aina seuraavat osat: Vaatimusmäärittely Toiminnallinen määrittely Tekninen suunnittelu Toteutus Testaus Tuotantoonsiirto Ylläpito Jukka Harju, 2006 3
Vaatimusmäärittely Vaatimusmäärittelyssä kuvataan ohjelmistolle asetettavat vaatimukset eli kuinka ohjelmiston tulee toimia. Vaatimukset saadaan ohjelmiston tilaajalta, joka suomalaisessa ohjelmistoteollisuudessa on useimmiten asiakkaana oleva yritys. Vaatimusmäärittely on korkean tason kuvaus, jota tarkennetaan alemman tason kuvauksissa (mm. toiminnallinen määrittelykuvaus ja suunnittelukuvaus). Esimerkki suorituskykyyn liittyvästä vaatimuksesta web-pohjaiselle järjestelmälle: Järjestelmän tulee pystyä palvelemaan vähintään 1000 yhtäaikaista käyttäjää alle 2 sekunnin vasteajoilla. Jukka Harju, 2006 4
Toiminnallinen määrittely Toiminnallisessa määrittelyssä kuvataan kaikki järjestelmän toteuttamat toiminnot ja liitännät järjestelmän ulkopuolelle. Toiminnallisen määrittelyn tuloksena syntyy määrittelydokumentti, joka kuvaa mitä järjestelmällä voi tehdä ja miten käyttäjä voi järjestelmää käyttää. Toiminnallinen määrittely ei ota kantaa siihen miten toiminnot tulee toteuttaa. Määrittelyn tulisi olla niin kattava, että seuraavissa vaiheissa ei ole enää epäselvää miten ohjelman tulee toimia eri tilanteissa. Tähän ei kuitenkaan käytännössä koskaan päästä. Toiminnallisessa määrittelyssä voidaan kuvata esimerkiksi webkäyttöliittymän eri näytöt ja näytöillä olevat komponentit. Määrittelydokumentin tulee sisältää tällöin sekä sanalliset että graafiset kuvaukset ko näytöistä. Jukka Harju, 2006 5
Tekninen suunnittelu Teknisen suunnittelu tarkoituksena on kuvata ohjelmiston tekninen arkkitehtuuri. Suunnittelussa kuvataan kuinka ohjelma suorittaa toiminnallisessa määrittelyssä kuvatut toiminnot. Suunnittelukuvauksesta ilmenee kuinka ongelma voidaan jakaa pienempiin, hallittavan kokoisiin osiin ja mitä kukin osa tekee. Olio-orientoinut suunnittelukuvaus kertoo mitä luokkia ja olioita tarvitaan ja mitkä ovat niiden keskinäiset suhteet. Sisältää mm. käytettyjen ohjelmistokomponenttien (luokkien) kuvaukset, niiden keskinäisten riippuvuuksien kuvaukset, tietovaraston kuvauksen jne. Kuvaus koostuu eri tasoista, esim. ohjelmataso, luokkataso, metoditaso. Kaikkein alimman tason osat kuvaukseen saa tuotettua esim. javadocista. Jukka Harju, 2006 6
Toteutus Toteutuksessa toteutetaan suunnittelukuvauksen mukainen lähdekoodi. Aloittelevat ohjelmoijat luulevat usein, että toteutus on ohjelmistoprosessin ydin, mutta itse asiassa sen tulisi olla vähiten luovuutta vaativa kohta. Lähes kaikki tärkeät päätökset tehdään jo vaatimusmäärittelyn ja suunnittelun aikana. Toteutuksen tulisi keskittyä ohjelmoinnin yksityiskohtiin, tyyliin (JCC) ja dokumentointiin (javadoc). Tuloksena on suoritettava ohjelmisto, joka yleensä kuitenkin sisältää suhteellisen paljon toiminnallisia virheitä. Jukka Harju, 2006 7
Testaus Testauksessa yritetään varmistaa, että ohjelma täyttää sille asetetut vaatimukset. Testauksessa yritetään löytää virheitä. Koko ohjelma tulee aina testata huolella. Testaus ei todista ohjelman toimivan oikein vaan todistetuksi tulee ainoastaan se, että ohjelma sisältää virheitä. Virheitä löytyy toteutuksen huolellisuudesta riippumatta lähes kaikista ohjelmistoista. Debuggaus on löydettyjen virheiden alkuperän etsimistä ja virheiden korjaamista. Jukka Harju, 2006 8
Tuotantoonsiirto Tuotantoonsiirrossa testattu ja toimivaksi todettu ohjelmisto asennetaan toimintaan tuotantoympäristöön. Tuotantoonsiirtoon liittyy usein mm. laitteiston asennus ja valmistelu, tietoliikenneyhteyksien valmistelu ja käyttäjien kouluttaminen. Jukka Harju, 2006 9
Ylläpito Ylläpito sisältää toimenpiteet, joita ohjelmistolle tarvitsee tuotantoonsiirron jälkeen tehdä, jotta asiakas olisi tyytyväinen ohjelmistoon. Laadukkaastikin ohjelmistosta löytyy useimmiten tuotantoonsiirron jälkeenkin pieniä virheitä, jotka tulee korjata. Käyttäjille tulee uusia tarpeita, joten ohjelmistoon on lisättävä uusia ominaisuuksia. Jukka Harju, 2006 10
Aina edellä kuvattu ei ole niin ongelmatonta Jukka Harju, 2006 11 lähde: www.monkeynoodle.org/comp/the_tree.pdf
Sisällys Ohjelmistosuunnittelusta Tarvittavien luokkien tunnistaminen Luokka-attribuutit ja -metodit Metodien suunnittelu Jukka Harju, 2006 12
Tarvittavien luokkien tunnistaminen Oliosuunnittelun tärkein tehtävä on määritellä ohjelmistossa tarvittavat luokat ja oliot sekä niiden väliset yhteydet. Luokat voivat olla Javan valmisluokkia, vapaasti saatavia Javan ulkopuolisia valmisluokkia (ns. 3rd party -luokkia), omia aiemmin tehtyjä luokkia tai täysin uusia. Eräs tapa tunnistaa mahdollisesti tarvittavat luokat on vaatimusmäärittelyn apuna käyttäminen. Oliot esiintyvät vaatimusmäärittelyssä useimmiten substantiiveina ja metodit verbeinä. Jukka Harju, 2006 13
Tarvittavien luokkien tunnistaminen Osa vaatimusmäärittelystä: Ohjelman tulee tuottaa asiakkaalle lähetettävä lasku. Lasku tulee kirjoittaa tekstitiedostoon. Lasku sisältää: 1. Myyjän nimen ja osoitteen 2. laskutuspäivän 3. Asiakkaan nimen ja osoitteen 4. Laskun identifioivan laskunumeron 5. Viitenumeron 6. Tilinumeron, jolle lasku maksetaan 7. Laskun muodostamisessa käytetyn tuntihinnan 8. Kaikki virheilmoitukset kirjoitetaan lokitiedostoon. Kaikista substantiiveista ei kuitenkaan tule luokkaa / oliota lopullisessa ratkaisussa! Jukka Harju, 2006 14
Tarvittavien luokkien tunnistaminen Luokka edustaa joukkoa samankaltaisia olioita. Luokat tulisi nimetä yksikkömuodossa olevilla substantiiveilla: Oppilas Osoite Viitenumero Poikkeuksiakin tietysti löytyy (esim. Tiedostotyokalut). Jukka Harju, 2006 15
Tarvittavien luokkien tunnistaminen Joskus on vaikea päättää tarvitaanko asian esittämiseen erillinen luokka. Esimerkiksi asiakkaan osoite voidaan esittää useampana erillisenä attribuuttina tai yhtenä Osoitetyyppisenä attribuuttina. Pääsääntönä on välttää kovin monimutkaisten luokkien tekemistä. Tällaiset luokat tulee pilkkoa useampaan pienempään luokkaan. Jukka Harju, 2006 16
Tarvittavien luokkien tunnistaminen Luokissa halutaan esittää tarvittava määrä yksityiskohtia. Esimerkiksi jokaista kulkuneuvoa kuvaamaan ei välttämättä tarvita omaa luokkaa, vaan voidaan käyttää yleisempää Kulkuneuvoluokkaa tarvittavin attribuutein. Tällöin Kulkuneuvo-luokan attribuuttina voi olla nimi, joka kertoo mikä laite on kyseessä. Esimerkiksi luokkien Auto, Moottoripyora ja Polkupyora sijaan saattaisi riittää luokka Kulkuneuvo, jolla voisi olla vaikkapa attribuutit nimi, kayttoonottovuosi ja omistaja. Jukka Harju, 2006 17
Tarvittavien metodien tunnistaminen Jokainen ohjelmassa suoritettava toiminto täytyy toteuttaa jonkin luokan metodina. Metodit nimetään käyttäen verbejä, esimerkiksi haeviitenumero, lasketarkiste. Suunnittelun alkuvaiheessa riittää tunnistaa kunkin luokan tärkeimmät tehtävät. Jukka Harju, 2006 18
Sisällys Ohjelmistosuunnittelusta Tarvittavien luokkien tunnistaminen Luokka-attribuutit ja -metodit Metodien suunnittelu Jukka Harju, 2006 19
Luokka-attribuutit ja -metodit Luokkametodia voidaan kutsua ilman yhtäkään oliota suoraan luokan nimen kautta. Esimerkiksi Javan Math-luokan metodit ovat luokkametodeja: double logaritmi = Math.log(2); Myös attribuutit voivat olla luokka-attribuutteja, tällainen on esimerkiksi Javan System-luokan outattribuutti: System.out.println("Kone on kaapattu Kuubaan"); Suunnittelun eräs vaihe on määritellä tuleeko attribuutin olla oliokohtainen vai luokka-attribuutti. Jukka Harju, 2006 20
static-avainsana Luokka-attribuutit ja luokkametodit määritellään staticavainsanaa käyttäen. static-avainsana määrittelee metodin tai attribuutin luokkakohtaiseksi (ei oliokohtaiseksi). Esimerkiksi. //luokka-attibuutti private static double tuntilaskutus; //luokkametodi public static double laskesumma(int a, int b) { return a + b; } Jukka Harju, 2006 21
Luokka-attribuutti Yleensä jokainen olio attribuutteineen sijaitsee omalla muistialueellaan. Luokka-attribuutista on kuitenkin olemassa vain yksi kopio, joka on yhteinen kaikille olioille. Luokka-attribuutti luodaan kun luokkaan viitataan ensimmäisen kerran. Luokka-attribuutin arvon muutos näkyy kaikille ko. luokan olioille. Jukka Harju, 2006 22
Luokka-attribuutti Luokka-attribuutti ei tule alustaa konstruktorissa, sillä tällöin luokka-attribuutille asetetaan aina oletusarvo uuden olion luonnin yhteydessä. Luokka-attribuutti voidaan alustaa staattisessa alustuslohkossa. Staattinen alustuslohko sijoitetaan luokassa heti attribuuttien jälkeen. Esimerkki. private static final double TUNTILASKUTUS; static { TUNTILASKUTUS = 125.0; } Luokka-attribuutteja käsittelevien get- ja set-metodien tulee olla luokkametodeja. Jukka Harju, 2006 23
Luokkametodi public class Laskin { public static long korotapotenssiin(int kantaluku, int eksponentti) { long tulos = kantaluku; for(int i=0; i < eksponentti; i++) { tulos = tulos * kantaluku; } return tulos; } } Koska metodi potenssi on luokkametodi, voidaan sitä kutsua seuraavasti (vrt. Math-luokka): long tulos = Laskin.korotaPotenssiin(5,3); Jukka Harju, 2006 24
Luokka-attribuutit ja -metodit main-metodi on aina luokkametodi (ns. staattinen metodi). Java-kääntäjä kutsuu sitä luomatta yhtään oliota. Luokkametodeista ei voida viitata oliokohtaisiin attribuutteihin (ilmentymäattribuutteihin), koska käytettävissä ei ole yhtään olioviittausta (luokkametodia ei kutsuta viittausmuuttujan kautta). Luokkametodista voidaan kuitenkin käyttää luokkaattribuutteja ja paikallisia muuttujia. Koodissa luokka-attribuutit kirjoitetaan aina ennen ilmentymäattribuutteja. Jukka Harju, 2006 25
Luokka-attribuutit ja -metodit import java.io.file; import java.io.ioexception; import java.util.scanner; import java.util.logging.level; /** * Laskutus-sovelluksen viitenumerokäsittelymetodit * sisältävä luokkakirjasto. * @author Jukka Juslin, Jukka Harju */ public class Viitenumero { /** * Palauttaa seuraavan käyttämättömän viitenumeron ja * päivittää sen tiedostoon viite.txt. * @return viitenumero */ public static String haeviitenumero() { //selvitetään seuraava käyttämätön viitenumero long seuraava; seuraava = viimeinenkaytettyviitenumero() + 1; //päivitetään viitenumero takaisiin tiedostoon try { TiedostoTyokalut.kirjoita( String.valueOf(seuraava), "viite.txt"); } catch (IOException ex) { TiedostoTyokalut.logger.log( Level.SEVERE, "Viimeisintä viitenumeroa ei pystytty " + "päivittämään viite.txt-tiedostoon"); } int eheys = lasketarkiste(seuraava + ""); String viitenumero = seuraava + "" + eheys; Kirjan Laskutus-ohjelma sisältää Viitenumero-luokan (s.78), jossa on luokkametodi haeviitenumero. Metodi hakee tiedostosta viimeksi käytetyn perusviitenumeron ja selvittää seuraavan käyttämättömän. Metodi myös päivittää viimeisimmän käytetyn viitenumeron. Metodin käyttää apuna saman luokan luokkametodeja viimeinenkaytettyviitenumero ja lasketarkiste sekä TiedostoTyokalut-luokan luokkametodia kirjoita. } return viitenumero; Jukka Harju, 2006 26
this-avainsana this-avainsana viittaa olioon itseensä. Metodin sisällä käytettynä this-avainsana siis viittaa olioon, jolle metodi suoritetaan. Esimerkiksi kutsuttaessa oliolle metodia setetunimi: oppilas1.setetunimi("anna"); oppilas2.setetunimi("jussi"); Ensimmäisessä kutsussa setetunimi-metodissa käytetty this-avainsana viittaa samaan olioon kuin oppilas1-viittausmuuttuja, toisessa samaan olioon kuin oppilas2-viittausmuuttuja. this-avainsana ei ole käytettävissä luokkametodeissa, miksi? Jukka Harju, 2006 27
this-avainsana this-avainsanalla voidaan erottaa attribuutti ja samanniminen paikallinen muuttuja toisistaan. Esimerkiksi setetunimi-metodissa: public void setetunimi(string etunimi) { if(etunimi!= null && etunimi.length() > 0) { this.etunimi = etunimi; } else { System.out.println("Etunimi ei saa olla tyhjä!"); } } Jukka Harju, 2006 28
Sisällys Ohjelmistosuunnittelusta Tarvittavien luokkien tunnistaminen Luokka-attribuutit ja -metodit Metodien suunnitteleminen Jukka Harju, 2006 29
Metodien suunnitteleminen J. G. Brookshear: "Algoritmi on äärellinen joukko täsmällisiä, suoritettavissa olevia ohjeita, jotka ohjaavat päättyvää tehtävän suoritusta." Esimerkiksi viitenumeron tarkisteen laskeminen: Viitenumerossa on aina tarkiste. Se lasketaan seuraavasti: Perusviitetiedoksi valitun luvun (esim. asiakas- tai laskunumeron) numerot kerrotaan oikealta vasemmalle painoilla 7, 3, 1, 7, 3,1. Tulot lasketaan yhteen, ja summa vähennetään seuraavasta täydestä kymmenestä. Erotus on tarkiste, joka merkitään viitenumeron viimeiseksi numeroksi. Jos erotus on 10, tarkiste on 0. (http://www.pankkiyhdistys.fi/sisalto/upload/pdf/viitenumero.pdf) Jokainen metodi toteuttaa jonkin algoritmin. Algoritmin ei tarvitse olla "monimutkainen". Algoritmi voidaan kirjoittaa suunnitteluvaiheessa pseudokoodina, joka on yhdistelmä Java-lauseita ja suomea. Algoritmi voidaan kuvata myös UML-toimintokaaviona. Jukka Harju, 2006 30
Metodin osittaminen Metodin tulee olla suhteellisen lyhyt looginen kokonaisuus. Näin koodi säilyy helpompana lukea. Pitkä metodi tulee pilkkoa useaan pienempään metodiin. public-tyyppinen metodi voi kutsua private-tyyppisiä (apu)metodeja toiminnon suorittamiseksi. Apumetodit voivat käyttää edelleen toisia apumetodeja. Jukka Harju, 2006 31
Metodin osittaminen Tarkastellaan esimerkkinä kirjan Laskutus-ohjelman Viitenumero-luokan metodia haeviitenumero. Metodi toteutus sisältää seuraavat toiminnot: 1. viimeisimmän käytetyn viitenumeron perusviitteen hakeminen tiedostosta 2. haetun arvon kasvattaminen yhdellä 3. kasvatetun perusviitteen kirjoittaminen takaisin tiedostoon 4. tarkisteen laskeminen perusviitteelle 5. perusviitteen ja tarkisteen yhdistäminen 6. tuloksen palauttaminen Jukka Harju, 2006 32
Metodin osittaminen Kokonaisuudessaan seuraavan käyttämättömän viitenumeron muodostaminen on liian laaja toimenpide suoritettavaksi yhdessä metodissa. Etsitään sopiva tapa jakaa ratkaisu osiin: toteutetaan viimeisimmän käytetyn perusviitteen hakeminen suojatussa viimeinenkaytettyviitenumero-metodissa. toteutetaan perusviitteen päivitys tiedostoon TiedostoTyokalut-luokan metodin kirjoita-avulla. Toteutetaan tarkisteen laskenta suojatussa lasketarkiste-metodissa. Jukka Harju, 2006 33
Metodin osittaminen Katso Viitenumero.java (sivu 78) UML-luokkakaaviossa attribuutin ja metodin näkyvyyden kuvaamisessa käytetään "+"- ja "-"-merkkejä. Julkisia komponentteja merkitään "+"-merkillä. Suojattuja komponentteja merkitään "-"-merkillä. Luokkakohtaisten komponenttien nimet esitetään alleviivattuina. Jukka Harju, 2006 34
Laskutus-ohjelma: UML-luokkakaavio Jukka Harju, 2006 35
Kertaus: Olion välittäminen parametrina Javassa parametrit välitetään aina arvoparametreina. Todellisena parametrina annettu (muuttujan) arvo kopioidaan metodille määritellyn paikallisen parametrimuuttujan alkuarvoksi. Parametrien välityksessä tapahtuu siis sijoittamista. Kun viittaus olioon välitetään parametrina, viittaavat sekä alkuperäinen viittausmuuttuja, että parametrimuuttuja samaan olioon. Jukka Harju, 2006 36
Kertaus: Olion välittäminen parametrina Kun olio välitetään parametrina, näkyy kutsutussa metodissa olion tietosisältöön tehty muutos kutsuneessa metodissa ilman paluuarvon välittämistä. Alkeistietotyyppisille muuttujille tehdyt muutokset eivät näy kutsuneelle metodille, vaan tällöin on aina käytettävä paluuarvon välittämistä. Jukka Harju, 2006 37
Metodin kuormittaminen Metodin kuormittaminen tarkoittaa saman metodinimen käyttämistä useammassa kuin yhdessä metodin määrittelyssä. Kuormitetun metodin tapauksessa pelkkä metodin nimi ei riitä kertomaan mitä metodia halutaan kutsua. Metodin signatuuri tarkoittaa metodin nimen ja parametrien yhdistelmää. Jokaisen (myös kuormitetun) metodin signatuurin täytyy olla uniikki. Kääntäjä päättää parametrien perusteella mitä kuormitetun metodin versiota kutsutaan. Jukka Harju, 2006 38
Metodin kuormittaminen Kutsu kirjoita( jotain, tiedosto.txt ); ohjautuu ao metodeista, ensimmäiselle, miksi? public static void kirjoita(string teksti, String tiedosto) {... } public static void kirjoita(stringbuffer teksti, String tiedosto) {... } Jukka Harju, 2006 39
Metodin kuormittaminen Java-APIsta havaitaan, että esimerkiksi String-luokan println-metodi on kuormitettu: println(string s) println(int i) println(double d) jne. Seuraavilla komennoilla voidaan kutsua näistä kahta eri versiota: System.out.println("Tulostetaan Merkkijono"); System.out.println(summa); Jukka Harju, 2006 40
Metodin kuormittaminen Metodin paluuarvo ei kuulu metodin signatuuriin. Kuormitetut metodit eivät siis voi erota pelkästään paluuarvon tietotyypin osalta. Myös konstruktoreja voidaan kuormittaa: Oletuskonstruktori Erilaiset parametrilliset konstruktorit Jukka Harju, 2006 41