Koko olio-ohjelmoinnin perusidea tiivistyy hyvin laadittuun olioon. Ilman kelvollista olioihin jakoa ja ohjelmointikuria olioita tehdessä on mahdollista luoda oliokielillä huomattavasti karmeampia luomuksia kuin perinteisillä kielillä.
Oliokielten voima saadaan esiin vasta noudattamalla myös niillä toteutettavien ohjelmien suunnittelussa ja toteutuksessa hyviä olioperiaatteita. Joitakin niistä esittelen tällä sivulla.
Loppupään kohdat painottuvat yhä enemmän asioihin jotka tuntuvat äkkikatsomalta merkityksettömän pieniltä ja nipotukselta. Ne ovat kuitenkin sellaisia kohtia, joissa pienen vaivan säästämällä joutuu isoon vaivaan tulevaisuudessa. Vasta itse moisien laiskottelujen suossa aikansa rämmittyään ymmärtää niiden merkityksen.
Ensimmäinen asia on olion rajaaminen. Olio-ajattelu lähtee siitä, että erillisen datan ja funktioiden sijasta paketoidaan sekä tieto että sen käsittelyyn tarvittavat operaatiot samaan pakettiin. Yksi olio sisältää siis jonkin järkevän kokonaisuuden tietoa (atribuutit ja alioliot) ja sen käsittelyyn tarvittavat operaatiot (metodit).
Karkeimmat virheet oliosuunnittelussa paljastuvat jo luokkien nimiä ja perustyönjakoa katsomalla. Jos luokan nimi on "AutonOvenAvaaja" tai "Renkaanvaihtaja" eikä "Auto" tai "Rengas" niin on ilmeistä että suunnittelija on paketoinut olioon operaatioita eikä tietoa.
|
Rakenteisessa ohjelmoinnissa on joukko operaatiokokonaisuuksia, joista laitetaan roikkumaan kulloistakin tietoa. Olio-ohjelmoinnissa on joukko tietokokonaisuuksia, joista laitetaan roikkumaan kunkin tiedon tarvitsemat operaatiot. |
Luokkamuuttujia ja luokkametodeja tulee käyttää erittäin harkiten. static ei ole oikotie. Jos mietit että "näitä olioitahan ei ole kuin yksi, joten voin tehdä sen static:ina, lopeta heti. Nyrkkisääntö on, että jos jonkin voi tehdä olioina, se tehdään olioina.
static-määreen käyttäminen oikaisemaan olioiden välisessä tiedonkulussa johtaa ennemmin tai myöhemmin ongelmiin. Yllättävän nopeasti ajaudut tilanteeseen jossa oikeastaan haluaisitkin kaksi ilmentymää ko. luokasta. Esimerkiksi kaksi samanlaista sovelmaa samalle sivulle tai saman sovelman kahdelle eri sivulle, joita silti voitaisiin katsoa saman istunnon aikana molempia. static:cina toteutettua "oliota" on myös paha välittää parametrina.
Sitä varten oliosuunnittelua tehdään, jotta olioiden välinen kommunikointi ja työnjako hoituisi ilman vippaskonsteja.
static-määre on tarkoitettu kuvaamaan sellaisia ominaisuuksia joita luokalla ei oikeasti koskaan, missään ole kuin yhden kerran, tai jotka eivät liity yhteenkään olioon. Muista että luokkamuuttujia ei ole kuin se yksi, koko virtuaalikoneessa. Ja että et koskaan voi tietää milloin ohjelmaasi ajetaan useampaa kappaletta samassa virtuaalikoneessa..
Älä koskaan (noh, on joitakin harvoja poikkeuksia) viittaa olion atribuutteihin ulkoa käsin. Määrittele kaikille ulkoa päin tarvittaville atribuuteille aksessorit.
Muista, että koskaan ei oliota pidä saada laittomaan tilaan sen metodeita käyttämällä. Käytä atribuutteja vain metodien kautta ja määrittele metodit niin että ne huolehtivat olion tilan säilymisestä järkevänä.
Esimerkki1, älä tee näin:
/** Tämä luokka toimii
esimerkkinä huonosta
kovakoodauksesta. **/
class Luokka {
int taulukko =
new int[10];
Button nimiPainike =
new Button("Nimi");
/* ************************ */
public void metodi() {
for (int i=0; i < 10; i++) {
foo(taulukko[i]);
}
if (aktiivinenPainike.
getLabel() == "Nimi") {
bar();
}
}
}
|
Esimerkki2, tee näin:
/** Tämä luokka toimii
esimerkkinä kovakoodauksen
välttämisestä. **/
class Luokka {
public static final int
TAULUKKO_MAX = 10;
public static final String
PAINIKKEEN_NIMI = "Nimi";
int taulukko =
new int[TAULUKKO_MAX ];
Button nimiPainike =
new Button(PAINIKKEEN_NIMI );
/* ************************ */
public void metodi() {
for (int i=0;
i < TAULUKKO_MAX;
i++) {
foo(taulukko[i]);
}
if (aktiivinenPainike.getLabel()
== PAINIKKEEN_NIMI) {
bar();
}
}
}
|
Älä käytä "paljaita" numeroita tai merkkijonoja koodin seassa. Vakiomäärittelyt ovat vakioita varten, ja jos taas kyseessä ei ole vakio, sitä suuremmalla syyllä älä laita siihen kiinteää numeroa.
Kun myöhemmin sitten joudut muuttamaan jotakin vakioarvoa tai peräti muuttamaan sen arvon muuttujaksi, et enää edes muista missä kaikkialla sitä käytetään. Suomeksi sanottuna, muuttamalla sitä yhdestä kohdasta rikot koko ohjelmasi. Jos olet käyttänyt määriteltyä vakiota, selvitä ainakin pienemmillä ongelmilla.
Käyttämällä esimerkin 2 tapaa, et aja NullPointerException:eihin tai ArrayIndexOutOfBoundsException:eihin ainakaan silkkaa typeryyttäsi.
Omien kokemuksieni perusteella metodien järkevää työnjakamista helpottaa kun kirjoittaa ensin metodille dokumenttikommentin. Tällöin joudut väkisin ensin miettimään että mitähän ihmettä se metodisi oikein tulee tekemään ja sitten vasta toteutat sen.
Metodin järkevä kokonaisuus ei yleensä ole yli 30 rivinen. Metodin järkevä kokonaisuus ei juuri koskaan ole yli 50 rivinen. Sinun ohjelmasi ei ole se juuri koskaan.
Jos metodillasi ei ole yhtä selkeää toimenpidettä jonka se suorittaa, ehkä se pitäisi jakaa osiin tai koostaa metodit toisin.
Esimerkki:
class Luokka {
public static void
main(String args[]) {
new Luokka(args).start();
}
}
|
Tyylipuhtaimmillaan main() ainoastaan luo ilmentymän isäntäluokastaan ja antaa sen hoitaa ohjelman suorituksen. Poikkeuksena esimerkiksi sovelmien main(), jossa yleensä myös luodaan ikkuna isäntäluokkaa varten.
Joka tapauksessa, en pidä hyvänä tyylinä suorittaa mitään prosessointia main():issa. Jos isäntäluokan pitää prosessoida jotain, se luo säikeen sitä varten.
Tähän käytäntöön on perusteluina kolme eri syytä: