T-76.611 Ohjelmistojen määrittely- ja suunnittelumenetelmät Harjoitustyöraportti TNT - Tarkistetaan Ne Tentit Arkkitehtuuri- ja suunnittelumalli Lasse Lindqvist Lasse Lopperi llindqvi@cc.hut.fi lmlopper@cc.hut.fi Andrey Rusanovich arusanov@cc.hut.fi
1 Johdanto Arkkitehtuurilla pyritään määrittämään suuren mittaluokan suunnittelulinjaukset ja järjestämään koko järjestelmän loogisiin, yhtenäisiin osakokonaisuuksiin - alijärjestelmiin. Järkevällä arkkitehtuurilla saavutetaan muun muassa ylläpidettävyyttä, modulaarisuutta, sitkeyttä (robustness) ja uudelleenkäytettävyyttä alijärjestelmien ollessa mahdollisimman vähän ja/tai yksisuuntaisesti toisistaan riippuvia. Arkkitehtuuri lienee myös paras väline kommunikoimaan järjestelmää esimerkiksi projektin uusille työntekijöille. Suunnittelu tarkentaa arkkitehtuurin kuvausta ja tarjoaa toteutusehdotuksen, jolla käyttötapaukset voidaan realisoida. Analyysin kertoessa mitä, suunnittelu kertoo siis miten. Käytännössä suunnitteluun kuuluu luokkakaaviot, jotka perustetaan enemmän tai vähemmän analyysimallista poimittuihin luokkiin. Näiden lisäksi tarvitaan avuksi muita luokkia, jotka eivät useinkaan vastaa mitään tosielämän oliota. Näitä saadaan usein kiinni viestiyhteyskaavioista, joissa kuvataan olioiden välistä kommunikaatiota eri käyttötapauksissa. Suunnittelussa kaikkiin luokkiin lisätään lisäksi operaatiot ja näkyvyydet, joilla määrätään niiden käyttäytyminen. 2 Arkkitehtuuri 2.1 Järjestelmän arkkitehtuuri Järjestelmämme voidaan jakaa perinteisen mallin mukaan kolmeen päällekkäiseen kerrokseen, josta ylin käsittelee käyttäjärajapinnat, keskimmäinen business-logiikan ja alin persistenssin. Tehtävänannon mukaisesti jätämme käyttäjärajapinnan ja persistenssin pitkältä huomioimatta ja keskitymme business-logiikkaan. 2.2 Horizontal layering Business-logiikka on jaettu sekin kolmeen alijärjestelmään, jotka voidaan nähdä olevan loogisesti rinnakkain käyttäjärajapinnan alla, niin että käyttäjärajapinnalla on näkyvyys kaikkiin näihin järjestelmiin. Alijärjestelmät ovat Kurssit, Tentit ja Henkilöt, joilla kaikilla on nimensä mukainen pääpainopiste. Alijärjestelmät voidaan myös järjestää niin, että ylempi riippuu aina vain alemmista. Tällöin Kurssit on ylin, Tentit sen alla ja Henkilöt alimmaisena. Useimmat luokat oli helppo sijoittaa keksittyihin alijärjestelmiin, mutta toiset, kuten Tehtävä (Tentit -järjestelmään Kurssit -järjestelmän sijaan) ja Rooli (Kurssit -järjestelmään Henkilöt -järjestelmän sijaan) olivat hieman epäselvempiä. Saavutimme kuitenkin lopulta mielestämme tarkoituksenmukaisen alijärjestelmäjaon joka pitää kytkeytymisen alhaisena ja yhtenäisyyden korkeana, ja jolla vältetään pakkausten väliset kehäriippuvuudet. Myös käyttöliittymätaso on jaettu kahteen erilliseen osakokonaisuuteen, toinen järjestelmän käyttäjiä ja toinen ylläpitoa varten. Nämä ovat selvästikin eri toimintoryhmät eivätkä missään vaiheessa mene päällekkäin, ja siksi pidimme näiden erottamista eri osajärjestelmiksi järkevänä. 2.3 Sijoittelu Järjestelmä voidaan sijoittaa eri koneille fyysisesti kolmitasomallia myötäillen. Tällöin solmuiksi muodostuvat tietokantapalvelin, http-palvelin ja http-asiakas. Käyttäjä- ja
ylläpitorajapinnat sijoitetaan http-palvelimelle. Sovelluslogiikka voidaan sijoittaa samaan solmuun http-palvelimen kanssa, joka tällöin käyttäisi sitä suoraan. Tämä tilanne kuvataan kuvan 2 sijoituskaaviossa. Tämä ei suinkaan ole ainoa sallittu vaihtoehto, vaan vaihtoehtoisesti sovelluslogiikka voidaan erottaa http-palvelimesta omaksi solmukseen, tai toisaalta koko järjestelmä asiakasta lukuun ottamatta voidaan sijoittaa samaan solmuun. Jaettaessa järjestelmä kolmeen solmuun palvelinpäässä koneet ovat yhdistetty esim. yksityisellä lähiverkolla. Yksi palvelintietokone toimii tietokantapalvelimena ja kuormasta riippuen yhdestä muutamaan palvelinta toimii WWW-palvelimina palvelinrypäässä. Käyttöliittymät ovat selainkäyttöisiä. Asiakaspääksi käy mikä tahansa tietokone, jolla on pääsy Internetiin ja johon on asennettu WWW-selain. 2.4 Arkkitehtuurikaaviot Kuva 1 Järjestelmän arkkitehtuuri Kuva 2 Kolmijakoinen sijoittelu
3 Suunnittelu 3.1 Realisoidut käyttötapaukset Suunnittelutason mallissa ja viestiyhteyskaavioissa toteutettaviksi käyttötapauksiksi valittiin tenttiin ilmoittautuminen, tehtävien pisteytys ja arvostelun hyväksyminen. Näiden ajateltiin olevan järjestelmän kannalta olennaisimpia toimintoja ja tulevan tiheimmin käytetyiksi. Edellä mainittujen lisäksi piirrettiin kaavio kirjautumisesta järjestelmää, koska se on esiehtona kaikille muille operaatioille. Kuva 3 Kirjaudu sisään
Kuva 4 Ilmoittaudu tenttiin
Kuva 5 Pisteytä tehtäviä
Kuva 6 Hyväksy arvostelu
3.2 Domain-mallin kehitys suunnittelumalliksi Jokaiseen pakkaukseen lisättiin julkisivuluokka XxxController, joka edelleen käyttää alijärjestelmän sisäisiä rajapintoja ja samalla agregoi siihen pakkaukseen kuuluvat oliot (kurssi / tentti / henkilö). Operaatioita luokkiin lisättiin pääosin tiedon välittämiseksi ja turhan kytkeytymisen välttämiseksi. Luokkien attribuuttien näkyvyydet muutettiin private:ksi ja tarvittaessa pääsy niihin luokan ulkopuolelta tapahtuu getter- ja setter-metodien kautta. Yksinkertaisuuden vuoksi monet suunnittelumallit on jätetty pakkauskohtaisista luokkakaavioista pois, mutta näiden käyttöä selitetään omassa kappaleessa. Business-logiikka alijärjestelmän sisällä ei käytetä rajapintoja vaan luokat kommunikoivat suoraan keskenään. Tämä johtuu siitä, että järjestelmän domain-malli on kohtuu staattinen eikä siihen ole odotettavissa merkittäviä muutoksia järjestelmän elinkaaren aikana. Tällöin myös business-logiikan toteutus pysyy samana. Käyttöliittymien ja erityisesti persistenssialijärjestelmän toteutuksien sen sijaan mukautuvan ajan myötä uusiin tekniikoihin, ja eristäviä rajapintoja tarvitaankin näiden ja business-logiikan väliin. Nämä on kuitenkin jätetty pois tästä osittaisesta suunnittelumallista. 3.3 Osittainen suunnittelumalli Ohessa ovat pakkauskohtaiset luokkakaaviot, joissa esitetään oleellisimmat luokat. Kaaviot pyrittiin pitämään kompakteina ja helppolukuisina, ja tästä syystä suunnittelumalleja ja apuluokkia on jätetty pois. Esimerkiksi kurssi-, tentti- ja henkilöinstanssien aggregaatio todennäköisesti toteutetaan HashMap tyyppisellä ratkaisulla, mitä ei kuitenkaan ole merkitty kaavioihin vaan XxxControllerista on suora aggragaatiosuhde vastaavaan luokkaan. Sama koskee myös muita vastaavia ratkaisuja joihin tässä dokumentissa ei oteta kantaa. Tämän sijasta keskitytään sovelluksen business-logiikan kannalta oleellisiin osiin. Kuva 7 Henkilöt -alijärjestelmä
Kuva 8 Kurssit -alijärjestelmä
Kuva 9 Tentit -alijärjestelmä 3.4 Suunnittelussa käytetyt (tai harkitut) suunnittelumallit Facade Kuten edellä oli jo mainittu, XxxController luokat toimivat julkisivuina pakkauksilleen tarjoten yksinkertaisen rajapinnan yleisimmille pakkaukseen kohdistuville toiminnoille, esimerkiksi kurssin ja siihen liittyvien tenttien/tehtävien haulle. Singleton + Factory Method XxxControllerit ovat luonteeltaan ainokaiset, sillä pakkauksen Controller aggregoi muiden pakkaukseen kuuluvien luokkien kaikkia ilmentymiä. Ainokainen on toteutettu private static attribuuttina, jolloin suorat viittaukset siihen luokan ulkopuolelta ovat kielletyt. Sen sijaan pääsy siihen tapahtuu getinstance() -metodin kautta, joka palauttaa viittauksen olemassa olevaan ainokaiseen tai luo ja alustaa sen jos kyseessä on
ensimmäinen metodin kutsu (vaihtoehtoisesti luonti ja alustus voidaan yhdistää järjestelmän käynnistykseen). Composite Kuva 10 Singeton + Factory method Kuten tiedetään tehtävä voi olla yksittäinen esseekysymys, tai koostua useammasta pienemmästä kysymyksestä, ja teoriassa mikään ei estä alijakoa seuraavillekin tasolle. Tämä tosiasia voidaan mallintaa suunnittelutason mallissamme rekursiokoosteena: abstraktista tehtäväluokasta periytyy muutama eri tehtävävaihtoehto ja lisäksi tehtäväkooste. Strategy Kuva 11 Tehtävä rekursiokooste Tehtävä(nanto) voidaan haluta näyttää ja käyttää erilaisissa muodoissa. Tällaisia voisi olla esimerkiksi http-sivu selainkäyttöliittymää varten, postscript/pdf tulostusta varten tai txt/doc lisämuokkausta varten. Tästä syystä on hyödyllistä tallentaa tehtävät geneeriseen muotoon (esim. xml) ja käyttää instanssia, joka toteuttaa konkreettisen toimintamallirajapinnan tehtävän muokkaamiseen haluttuun tiedostomuotoon.
Prototype Kuva 12 Tulostusstrategia Voidaan olettaa, että useat tentit ja/tai tehtävät sisältävät paljon samaa. Vähintäänkin kurssikoodi ja nimi toistuvat pitkälti samanlaisina. Lisäksi tehtävien määrä ja pisteet ovat nekin usein samat. Näin ollen uusien tenttien luontia voidaan helpottaa käyttämällä valmiita tenttipohjia. Nämä pohjat voidaan toteuttaa Tentti-luokan prototyyppiilmentyminä. Pohja voidaan alustaa vain kertaalleen ja kutsua tarvittaessa sen clone() - metodia sen sijaan, että joka kerta luotaisiin uusi alustamaton ilmentymä ja asetettaisiin samat muutokset sen setter-metodeita käyttäen. Tässäkin voidaan käyttää singleton ja factory method -suunnittelumalleja määrittelemällä esimerkiksi staattinen gettemplate(string key) -metodi, joka palauttaa avainta vastaavan templaten staattisesta Hashtable:sta. Template Method Vaatimuksissa oli maininta automaattisesti tarkistettavista tehtävistä, ja vaikka tätä toiminnallisuutta ei edellytetä vielä järjestelmän ensimmäiseltä versiolta, päätimme ottaa sen huomioon suunnittelussa. Tehtävä-kantaluokkaan voidaan määritellä pisteytä() - metodi, joka korvataan esim. Not Supported poikkeuksen heittävällä toteutuksella käsin tarkastettavien tehtävien tapauksessa. Automaattisesti tarkistettavat tehtävän aliluokat sen sijaan suorittavat tehtävän pisteytyksen ja palauttavat tuloksen.
Bridge Kuten edellä olikin mainittu järjestelmämme business-logiikan ei odoteta vaativaan muutoksia, ja tästä syystä luokkien välissä ei käytetä rajapintoja. Käyttöliittymä ja persistenssitoteutukset voivat sen sijaan muuttua. Näiden alijärjestelmien ja businesslogiikan välille tarvitaankin rajapinnat, jotta business-logiikka olisi riippumaton näiden toteutuksista, eikä vaatisi näin muutoksia koodiin alijärjestelmien muuttuessa. Observer Observer-mallia harkittiin muun muassa Tenttisuoritus- ja Tehtäväsuoritus -luokkien välille, jotta tentin kokonaispistemäärä päivittyisi tehtäviä arvosteltaessa. Käytölle ei kuitenkaan ollut tarpeeksi edellytyksiä, sillä Tenttisuoritus-olio tietää muutenkin kaikista tehtäväsuorituksistaan ja voi johtaa kokonaispistemäärän aina tarvittaessa. Lisäksi pistemäärälle on tarvetta vain arvostelun lukitus/hyväksymisvaiheessa. Sen sijaan, jos tietoa voitaisiin haluta mielivaltaisella hetkellä ja useita kertoja ennen arvostelun lukitusta, olisi ehkä kannattavaa pyytää suorituksia laskemaan pistemääränsä vain tehtäväpisteiden muuttuessa ja palauttaa tämä valmiiksi laskettu arvo vastauksena kyselyihin.