T-76.115 Henkilökohtainen harjoitus: FASTAXON Suunnittelumallit Group: Muuntaja Pentti Vänskä 52572W
2 1. Toteutus Tämä henkilökohtainen harjoitustyö käsitteli suunnittelumallien (Design Patterns) käyttöä Fastaxon-projektissa. Suunnittelumalleja pyrittiin soveltamaan, mikäli niiden käyttö näytti olevan projektin kannalta hyödyllistä. Suunnittelumalleja ei yritetty soveltaa väkisin tämän projektin ongelmiin. Käytännössä suunnittelumallien käyttö rajoittui koodin malliosaan (Model). Tässä dokumentissa esitellään ne suunnittelumallit, jotka tulivat tietoisesti malliin mukaan. Koodista voi hyvin löytää paljon muita suunnittelumalleja, joita ei tässä dokumentissa esitellä. 2. Suunnittelumallien jaottelu Suunnittelumallit jaetaan käyttötarkoituksen mukaan kolmeen päätyyppiin, jotka ovat seuraavia [1, s.10; 3, 12]: 1. creational 2. structural 3. behavioral Creational-mallit ovat objektien luomiseen liittyviä malleja. Näiden mallien avulla objektien luominen tavallaan automatisoidaan. Structural-malleja käytetään luotaessa olioita suuremmiksi kokonaisuuksiksi. Behavioral-mallit on tehty helpottamaan objektien välistä kommunikointia. Tässä dokumentissa esitellään jaottelun mukaisesti seuraavat suunnittelumallit: 1. creational-mallit singleton abstract factory 2. structural-mallit façade proxy 3. behavioral-mallit command pattern 3. Mallien käyttö Fastaxon-projektissa Kaikki mallit on otettu käyttöön projektin I2 ja I3-vaiheissa. Tässä dokumentissa esiteltävät mallit ovat edelleen käytössä. Fastaxonprojektissa on sovellettu Model View Controller - arkkitehtuuria, jossa siis ohjelma jaetaan kolmeen pääosaan: malliin, näyttökomponentteihin ja niitä yhdistävään kontrolleriin. Tätä arkkitehtuuria voidaan sinällään pitää jo yhtenä suunnittelumallina. 2
3 Singleton Singleton-suunnittelumallin avulla tietystä luokasta luodaan vain yksi instanssi hallitusti [1, s. 127; 2 s. 31]. Projektin mallissa (model) ModelContext on singleton-tyyppinen luokka. Luokan ainoa instanssi saadaan getinstance-metodin avulla. Tämä luokan kautta kaikki mallin muut oliot luodaan, siksi juuri singleton on tähän sopiva. Vastaavasti myös mallin DBPool on singleton-tyyppinen luokka. Myös tämän luokan ainoa instanssi saadaan getintance-metodilla. DBPool-luokan kautta saadaan tietokantayhteyksien määrä pidettyä tietyssä rajassa. Luokassa luodaan tietty määrä DBHelper-olioita, joista yksi edustaa yhtä yhteyttä. Luokan DBPool getinstance-metodin koodi näyttää seuraavalta: public synchronized static DBPool getinstance() throws DatabaseException { if (instance == null) { int max = Integer.parseInt((String) ModelContext.getInstance().getParametres().get(MAX_CONNECTIONS)); instance = new DBPool(); helpers = new Stack(); boolean standalone = false; if (ModelContext.getInstance().getParametres().get(STANDALONE).equals("true")) { standalone = true; for (int i = 0; i < max; i++) { helpers.push(new DBHelper(standalone)); catch(modelexception e){ throw new DatabaseException(e); return instance; Kuten koodista huomataan, luodaan DBPool-luokan instanssi, jos sitä edustava instance-kentän arvo on null. Intance-kenttä on tyypiltään staattinen eli kenttämääritys on seuraava: private static DBPool instance = null; DBHelpers-oliot talletetaan pinoon, josta niitä jaetaan tarpeen mukaan gethelper-metodilla, jonka koodi näyttää seuraavalta: public synchronized DBHelper gethelper() throws DatabaseException { while (helpers.isempty()) { wait(); catch (InterruptedException e) { throw new DatabaseException(e); return (DBHelper) helpers.pop(); Koodista voidaan havaita, että kutsuva säie (engl. thread) asetetetaan wait-tilaan wait-metodin avulla eli tällöin säikeen suoritus pysähtyy, 3
4 jos DBHelper-olioiden pino helpers on tyhjä. Käytännössä tilanne, jolloin pino on tyhjä, tarkoittaa se sitä, että maksimimäärä tietokantayhteyksiä on käytössä. Wait-tilassa olevat tietokantayhteydet vapautuvat, kun wait-tilassa oleva säie saa notifymetodilla jatkamiskäskyn (=säie herätetään), jolloin kyseinen säie jatkaa while-silmukassa suoritusta, jossa tarkistetaan onko helpersolioiden pino edelleen tyhjä. Jos pino ei ole, tyhjä palauttaa gethelper-metodi helpers-pinosta DBHelper-olion. Säie saa notifymetodilla jatkamiskäskyn metodissa releasehelper, jonka koodi näyttää seuraavalta: public synchronized void releasehelper(dbhelper helper) { helpers.push(helper); notify(); Mallissa releasehelper-metodia kutsutaan sen jälkeen, kun kutsuva säie ei enää tarvitse tietokantayhteyttä. Javassa on käytössä ns. signal and continue -tyyppinen moniajosysteemi, johon kaikki edellä esitetty periaatteessa perustuu. Singleton-suunnittelumallin käyttö on tässä yhteydessä hyvin perusteltua, jotta yhteyksien jakaminen ja vapauttaminen tapahtuisi hallitusti. Tälle suunnittelumallille on vaikea keksiä vaihtoehtoista mallia, ja siitä on lähes korvaamaton hyöty tässä projektissa. Abstract factory Abstract factory -suunnittelumallin avulla tuotetaan olioita annetun syöttötiedon perusteella. Erona pelkkään Factory-sunnittelumalliin on se, että tässä suunnittelumallissa olioiden toteuttavat luokat piilotetaan ja ne voidaan tarvittaessa vaihtaa [1, s.87; 2 s. 26]. Projektin mallissa (model) rajapinnat ja niiden toteuttavat luokat on talletattu ModelContext-luokan parametritietoihin siten, että kunkin rajapinnan toteuttava luokka on haettavissa. Metodilla setparametres asetetaan mallin parametrit HashMap-oliona. Metodi getmodelfactory palauttaa mallin ModelFactory-rajapinnan toteuttavan olion. Metodin javakoodi näyttää seuraavalta: public synchronized ModelFactory getmodelfactory() throws ModelException { if (factory == null) { String implclassname = (String) getparametres().get("fi.vtt.fastaxon.model.interfaces.modelfactory"); Class implclass = Class.forName(implClassName); factory = (ModelFactory) implclass.newinstance(); catch (Exception e) { throw new ModelException(e); return factory; 4
5 Vastaavaan tapaan muut mallin rajapintojen oliot luodaan ModelFactory-luokan getmodel-metodeilla, joka palauttaa halutun olion annettujen parametritietojen avulla. Metodista on kolme ylikuormitettua versiota eri käyttötapauksia varten. Metodin javakoodi näyttää seuraavalta: public Object getmodel(string modelinterfaceclassname, Object[] parametres) throws ModelException { Object model = null; boolean cacheable = false; ModelContext context = ModelContext.getInstance(); String classimpl = (String) context.getparametres().get(modelinterfaceclassname); Class modelclass = Class.forName(classImpl); if (iscacheable(modelclass)) { cacheable = true; model = getcache().getmodel(modelinterfaceclassname, parametres); if (model!= null) return model; Constructor constr[] = modelclass.getconstructors(); for (int i = 0; i < constr.length; i++) { model = constr[i].newinstance(parametres); if (model!= null) break; catch (Exception e) { catch (ClassNotFoundException e) { throw new ModelException(e); if (model == null) throw new ModelException(EXP_CANNOT_GET_MODEL, "get model"); if (cacheable) getcache().addmodel(modelinterfaceclassname, parametres, model); return model; Koodin perusideana on Class-luokan metodit, joiden avulla rajapinnan toteuttavan luokan Class-olio saadaan luotua. Kyseisen luokan sopivalla konstruktorilla luodaan annetun parametritaulukon avulla itse luokan instanssi. Tässä koodissa on lisäksi mukana kätkömuistin käsittely, joka tuo muutaman rivin tähän lisää. Lopputuloksena saadaan aikaan järjestely, jossa haluttu olio saadaan seuraavaan esimerkin tapaan: Facet facet = (Facet)modelContext.getModelFactory().getModel("fi.vtt.fastaxon.model.interfaces.Facet", parametres); Parametreina annetaan siis rajapinnan String-olio sekä Objecttyyppinen taulukko, joka sisältää konstruktorin tarvitsemat parametrit. Tämän suunnittelumallin hyöty on suuri tässä projektissa. Tälle suunnittelumallille vaihtoehtona voisi olla esimerkiksi sellainen malli, jossa oliot luodaan suoraan new-operaattorilla. Tämä malli ei kylläkään ole vaihtoehtoinen suunnittelumalli. 5
6 Façade Façade-suunnittelumallin avulla monimutkaisen oliomallin käyttöä pyritään helpottamaan siten, että mallin käyttäminen yksinkertaistetaan tekemällä erityinen luokka, jonka kautta oliomallia käytetään [1 s. 185; 2 s. 111]. Projektin mallin ModelContext luokkaa voidaan pitää façadesuunnittelumallin mukaisena edustaluokkana. ModelContext-luokan kautta saadaan Designer-olio, jonka kautta saadaan suunnittelijan Context-olio, jolla on päästään käsiksi ko. suunnittelijan projekteihin. Malliin oli tarkoituksena toteuttaa sellainen esto, jolla estettäisiin new-operaattorin käyttö rajapintojen toteuttaviin luokkiin. Tämä esto ei kuitenkaan toimi nyt, koska luokkien konstruktorit ovat publictyyppisiä. Tällä estolla oliomallin väärin käyttö olisi mahdotonta. Tästä suunnittelumallista on varsin suuri hyöty, koska sen avulla oliomallin käyttäminen on helpompaa. Vaihtoehtoisesti oliomallia voisi käyttää suoraan ilman ohjaavaa luokkaa. Proxy Proxy-suunnittelumallin avulla luodaan aikaiseksi sellainen järjestely, jossa varsinaisen käyttöolion taakse piilotetaan suorittavaolio tai olioita, joilla on oma tarkoituksen mukainen tehtävä. Tätä suunnittelumalli on käyttökelpoinen esimerkiksi seuraavissa tilanteissa [1 s. 207; 2 s. 124]: 1. oliossa on toiminto, jonka suorittaminen vie paljon aikaa 2. erilaisten oikeuksien jakaminen olioiden välillä Projektin mallissa kaikki mallin rajapintojen toteuttavat luokat ovat periaatteessa proxy-suunnittelumallin tyyppisiä luokkia. Toteuttavissa luokissa on paljon sellaisia metodeja, joita ei ole määritelty rajapinnoissa. Toteuttavat luokat kutsuvat myös DBHelper-luokan metodeja eli osa luokkien toiminnasta on eriytetty toiseen luokkaan. Nämä DBHelper-luokan metodit on suojattu niin, että niitä ei voi käyttää paketin ulkopuolelta. Tämä suunnittelumalli on varsin hyödyllinen oliomallin kannalta. Vaihtoehtoisesti kaikki luokan toiminta voisi olla samassa yhdessä luokassa. Command pattern Command pattern -suunnittelumallin ideana on luoda mahdollisuus olioiden väliseen kommunikointiin tietyllä sovitulla tavalla [1 s. 233; 2, s.139]. 6
7 Projektin mallissa on rajapinta Model, jossa on määritelty metodit, joilla mallin olioiden välinen tiedonvälitys on mahdollista järjestää. Käytännössä mikä tahansa olio voi kuunnella toista oliota ja saada tältä viestejä. Rajapinnan metodit ovat seuraavia: - addmodellistener - removemodellistener - notifymodellisteners - modelchanged Tässä Command pattern -suunnittelumallin käyttö on tarkemmin vain metodissa modelchanged, jota kutsutaan olion muutostilanteessa. Muilla metodeilla luodaan systeemi, jossa olioon voidaan liittää kuuntelija addmodellistener-metodilla, poistaa kuuntelija removelistener-metodilla sekä viestittää kuuntelijoita notifymodellisteners. Lisäksi systeemissä on ModelEvent-luokka, joka on sisältää kaiken viestitettävän informaation. Metodi notifymodellisteners välittää ModelEvent-olion rekursiivisesti kaikille olion kuuntelijoille. Koodi näyttää seuraavalta: public synchronized void notifymodellisteners(modelevent modelevent) throws ModelException { if (modelevent.getmodel() == null) modelevent.setmodel(this); recursemodels(modelevent); protected void recursemodels(modelevent modelevent) throws ModelException { Iterator iterator = getlisteners().values().iterator(); while (iterator.hasnext()) { Model model = (Model) iterator.next(); modelevent.setsender(this); model.modelchanged(modelevent); ((ModelImpl) model).recursemodels(modelevent); Tämän mallin hyöty jäi varsin vähäiseksi, koska projektin aika alkoi käydä vähiin ja mallia ei ehditty soveltamaan kunnolla. Vaihtoehtoisesti oliomallissa voisi olla sellainen systeemi, jossa kussakin luokassa olisi tietyt ylläpitometodit (esimerkiksi remove ja update metodit) Command -mallin tapaan. Itse asiassa kysymyksessä olisi tämän saman suunnittelumallin toisenlainen toteutus. 7
8 LÄHTEET 1. Gamma Eric, Helm Richard, Johnson Ralph, Vlissides John, Design Patterns 2. Cooper James W., The Design Patterns Java Companion 8