Tässä ohjeessa esitellään muutamia yksinkertaisia tapoja pienen sovelluksen toteuttamiseen.
Ratkaisut perustuvat käyttöliittymän ja tietorakenteen erottamiseen toisistaan. Käyttöliittymäluokka on sovelluksen "pääohjelmaluokka", joka käyttää abstraktia tietorakennetta (kts. Johdatus ohjelmointiin, luku 4.3). Abstrakti tietotyyppi on toteutettu luokkana, joka kätkee tietorakenteen konkreettisen toteutuksen ja tarjoaa operaatioita arvojen muutteluun ja kyselemiseen.
Sovelluksen pääohjelmaluokka siis
Tehtävä: Toteuta graafiseen käyttöliittymään perustuva sovellus Laskurisovellus, jolla käyttäjä voi laskea nappulan painalluksia. Tällaista laskuria voisi käyttää vaikkapa lampaiden laskemiseen: paina nappulaa aina kun lammas hyppää aidan yli ... Laskuri täytyy myös voida asettaa uudelleen alkutilaan. Laskurilla on jokin vakioyläraja, jota ei saa ylittää. Esimerkeissä yläraja on 10 (rajaa on helppo muuttaa).
Sovelluksen toiminnot:
Huom: Abstraktin tietotyypin hyvä suunnittelu - mitä ja millaisia operaatioita - on hyvin oleellinen ja kriittinen tehtävä! Se suurelta osin määrää koko ratkaisun selkeyden ja tyylikkyyden. Jos tietotyyppiä käytettäessä tulee halu päästä käsiksi toteutuksen yksityiskohtiin, operaatiot on luultavasti määritelty epätarkoituksenmukaisesti! Tietotyypin toteutus, Luokka Laskuri.java:
////////////////////////////////////////////// Arto Wikla 1998 /////////// // // Luokka Laskuri: toteuttaa ylöspäinlaskurin. // // Konstruktori: // // public Laskuri(int raja) // luo Laskurin joka aloittaa nollasta ja jonka viimeinen sallittu arvo // annetaan parametrina. Jos parametri on negatiivinen, luodaan Laskuri, // jonka viimeinen sallittu arvo on nolla // // Aksessorit: // // public int mikaArvo() // palauttaa Laskurin arvon. // // public boolean lopussa() // palauttaa tiedon siitä, onko laskurin arvo jo saavuttanut ylärajan, // arvo on true, jos yläraja on saavutettu // // Operaatiot: // // public void kasvata() // kasvattaa Laskurin arvoa, jos ylärajaa ei ole saavutettu. // // public void nollaa() // nollaa Laskurin arvon. // ////////////////////////////////////////////////////////////////////////// public class Laskuri { // tietorakenteen toteutuksen muuttujat: private final int YLARAJA; // konstruoitavan laskurin viimeinen arvo private int arvo; // laskurin arvo 0, 1, 2, ..., YLARAJA // Konstruktori: public Laskuri(int raja) { arvo = 0; if (raja > 0) YLARAJA = raja; else YLARAJA = 0; } // Aksessorit: public int mikaArvo() { return arvo; } public boolean lopussa() { return arvo >= YLARAJA; } // Operaatiot: public void kasvata() { if (!lopussa()) { ++arvo; } } public void nollaa() { arvo = 0; } }
Ensimmäisessä versiossa konstruktori kantaa koko vastuun käyttöliittymän elementtien alustuksesta, Laskurisovellus1.java:
//////////////////////////////////////////// Arto Wikla 1998 ///////////// // // Luokka Laskurisovellus1 tarjoaa käyttäjälle laskurin, joka laskee // nappulan painamisia arvoilla 1, ..., 10. Ylärajaa on helppo muuttaa, // kts. ohjelman alussa oleva vakio LASKURIN_KOKO. // // Sovellus tarjoaa myös mahdollisuuden uudelleenaloitukseen eli // laskurin nollaamiseen. // // Sovellus käyttää laskurina abstraktin tietotyypin Laskuri tarjoamia // välineitä. Kts. rajapinnan määrittelyä tiedoston Laskuri.java alussa. // // Käännösyksikön lopussa on luokka HoiteleIkkunanSulkeminen, // kts. Johdatus ohjelmointiin, luku 6.5. // ////////////////////////////////////////////////////////////////////////// import java.awt.*; import java.awt.event.*; public class Laskurisovellus1 extends Frame implements ActionListener { private final int LASKURIN_KOKO = 10; // laskurin viimeinen arvo private final Laskuri laskuri; // muuttuja abstraktille tietorakenteelle // käyttöliittymän elementtikentät: private final TextField laskurinArvo; private final Button kasvata; private final Button nollaa; private final Button lopeta; //----- sovelluksen konstruointi: ----// public Laskurisovellus1() { laskuri = new Laskuri(LASKURIN_KOKO); // tietorakenteen luonti // käyttöliittymän elementtien arvot: laskurinArvo = new TextField(5); laskurinArvo.setText("ALUSSA!"); laskurinArvo.setEditable(false); kasvata = new Button("kasvata"); nollaa = new Button("aloita alusta"); lopeta = new Button("lopeta"); // tapahtumankuuntelijoiden asettaminen: kasvata.addActionListener(this); nollaa.addActionListener(this); lopeta.addActionListener(this); // näkymän lay-outin luonti: add("North", laskurinArvo); add("West", kasvata); add("East", nollaa); add("South", lopeta); // ikkunan sulkemisen kuuntelijan asettaminen: addWindowListener(new HoiteleIkkunanSulkeminen()); } //----- tapahtumankäsittelijä: -----// public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); if (aiheuttaja == kasvata) kasvatus(); else if (aiheuttaja == nollaa) nollaus(); else if (aiheuttaja == lopeta) lopetus(); } //----- tapahtumaoperaatiot: -----// // (käytetään abstraktin tietotyypin Laskuri tarjoamia operaatioita) private void kasvatus() { if (!laskuri.lopussa()) { laskuri.kasvata(); laskurinArvo.setText(""+laskuri.mikaArvo()); } else laskurinArvo.setText("LOPPU!"); } private void nollaus() { laskuri.nollaa(); laskurinArvo.setText("ALUSSA!"); } private void lopetus() { System.exit(0); // ikkunan sulkeminen } //----- sovelluksen pääohjelma: -----// public static void main(String[] args) { Laskurisovellus1 ikkuna = new Laskurisovellus1(); ikkuna.setTitle(ikkuna.LASKURIN_KOKO+"-laskuri"); ikkuna.pack(); ikkuna.setVisible(true); } } //----- ikkunan sulkemisen hoitelu: -----// class HoiteleIkkunanSulkeminen extends WindowAdapter { public void windowClosing(WindowEvent tapahtuma) { System.exit(0); // ikkunan sulkeminen } }
Käyttöliittymän elementit voidaan luoda myös jo määrittelyn yhteydessä, jolloin konstruktorille jää vähemmän tehtävää (Laskurisovellus2.java):
... private final int LASKURIN_KOKO = 10; // laskurin viimeinen arvo private final Laskuri laskuri; // muuttuja abstraktille tietorakenteelle // käyttöliittymän elementit: private final TextField laskurinArvo = new TextField(5); private final Button kasvata = new Button("kasvata"); private final Button nollaa = new Button("aloita alusta"); private final Button lopeta = new Button("lopeta"); //----- sovelluksen konstruointi: ----// public Laskurisovellus2() { laskuri = new Laskuri(LASKURIN_KOKO); // tietorakenteen luonti // laskurinArvo-muuttujan alustus: laskurinArvo.setText("ALUSSA!"); laskurinArvo.setEditable(false); // tapahtumankuuntelijoiden asettaminen: kasvata.addActionListener(this); nollaa.addActionListener(this); lopeta.addActionListener(this); // näkymän lay-outin luonti: add("North", laskurinArvo); add("West", kasvata); add("East", nollaa); add("South", lopeta); // ikkunan sulkemisen kuuntelijan asettaminen: addWindowListener(new HoiteleIkkunanSulkeminen()); ...
Huom: Ilmentymäalustuslohkot ovat Javan version 1.1.* uutuus, Niitä ei käsitelty kurssilla!)
... private final int LASKURIN_KOKO = 10; // laskurin viimeinen arvo private final Laskuri laskuri; // muuttuja abstraktille tietorakenteelle // käyttöliittymän elementit, niiden alkuarvot ja kuuntelijat: private final TextField laskurinArvo = new TextField(5); { laskurinArvo.setText("ALUSSA!"); laskurinArvo.setEditable(false); } private final Button kasvata = new Button("kasvata"); { kasvata.addActionListener(this); } private final Button nollaa = new Button("aloita alusta"); { nollaa.addActionListener(this); } private final Button lopeta = new Button("lopeta"); { lopeta.addActionListener(this); } //----- sovelluksen konstruointi: ----// public Laskurisovellus3() { laskuri = new Laskuri(LASKURIN_KOKO); // tietorakenteen luonti // näkymän lay-outin luonti: add("North", laskurinArvo); add("West", kasvata); add("East", nollaa); add("South", lopeta); // ikkunan sulkemisen kuuntelijan asettaminen: addWindowListener(new HoiteleIkkunanSulkeminen()); ...
Huom: Myös lay-out asetukset, elementtien asemointi, voitaisiin tehdä jo ilmentymäalustuslohkoissa, mutta se ei olisi viisasta!
Vaihtoehdon valinta on osittain makuasia, osittain se riippuu tarpeista. Jos vaikkapa konstruktorin parametrin tulee vaikuttaa alkuarvoihin, alustukset on tehtävä konstruktorissa. Tehdäänkö siellä silloin kaikki alustukset vai vain tarpeelliset? Asetetaanko oletusalkuarvot määrittelyiden yhteydessä?
Valintavaihtoehtoja on paljon. Selkeys taas kerran ratkaiskoon!
public class Laskurisovellus1 extends Frame implements ActionListener {(kts. rajapintaluokan ActionListener määrittely)
Javan nimettömillä sisäluokilla (anonymous inner classes) tapahtumankäsittely voidaan ohjelmoida käyttöliittymäelementeittäin, hajautetusti. Tätä tapaa voidaan ehkä pitää "oliohenkisempänä" kuin edellistä.
Toteutetaan Laskurisovellus4.java muokkaamalla ensimmäistä esimerkkiä:
//////////////////////////////////////////// Arto Wikla 1998 ///////////// // // Luokka Laskurisovellus4 tarjoaa käyttäjälle laskurin, joka laskee // nappulan painamisia arvoilla 1, ..., 10. Ylärajaa on helppo muuttaa, // kts. ohjelman alussa oleva vakio LASKURIN_KOKO. // // Sovellus tarjoaa myös mahdollisuuden uudelleenaloitukseen eli // laskurin nollaamiseen. // // Sovellus käyttää laskurina abstraktin tietotyypin Laskuri tarjoamia // välineitä. Kts. rajapinnan määrittelyä tiedoston Laskuri.java alussa. // ////////////////////////////////////////////////////////////////////////// import java.awt.*; import java.awt.event.*; public class Laskurisovellus4 extends Frame { private final int LASKURIN_KOKO = 10; // laskurin viimeinen arvo private final Laskuri laskuri; // muuttuja abstraktille tietorakenteelle // käyttöliittymän elementtikentät: private final TextField laskurinArvo; private final Button kasvata; private final Button nollaa; private final Button lopeta; //----- sovelluksen konstruointi: ----// public Laskurisovellus4() { laskuri = new Laskuri(LASKURIN_KOKO); // tietorakenteen luonti // käyttöliittymän elementtien arvot: laskurinArvo = new TextField(5); laskurinArvo.setText("ALUSSA!"); laskurinArvo.setEditable(false); kasvata = new Button("kasvata"); nollaa = new Button("aloita alusta"); lopeta = new Button("lopeta"); // tapahtumankuuntelijoiden asettaminen ja tapahtumien käsittely: kasvata.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent tapahtuma) { if (!laskuri.lopussa()) { laskuri.kasvata(); laskurinArvo.setText(""+laskuri.mikaArvo()); } else laskurinArvo.setText("LOPPU!"); } } ); nollaa.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent tapahtuma) { laskuri.nollaa(); laskurinArvo.setText("ALUSSA!"); } } ); lopeta.addActionListener( new ActionListener () { public void actionPerformed(ActionEvent tapahtuma) { System.exit(0); // ikkunan sulkeminen } } ); // näkymän lay-outin luonti: add("North", laskurinArvo); add("West", kasvata); add("East", nollaa); add("South", lopeta); // ikkunan sulkemisen kuuntelijan asettaminen ja sulkemisen käsittely: addWindowListener( new WindowAdapter () { public void windowClosing(WindowEvent tapahtuma) { System.exit(0); // ikkunan sulkeminen } } ); } //----- sovelluksen pääohjelma: -----// public static void main(String[] args) { Laskurisovellus4 ikkuna = new Laskurisovellus4(); ikkuna.setTitle(ikkuna.LASKURIN_KOKO+"-laskuri"); ikkuna.pack(); ikkuna.setVisible(true); } }Huom:
Huom: Sisäluokat ja private-määritellyt asiat saattavat aiheuttaa ongelmia! (Esimerkkiluokka X.java jumiuttaa kääntäjän (versio 1.1.3!)) (Sun ilmoitti minulle 26.1.1998, että kyseinen virhe on korjattu JDK:n versiossa 1.1.5.)