Kapselointi
osaamistavoitteet
- Tiedät, mitä näkyvyysmääreet kuten
publicjaprivatetarkoittavat. - Ymmärrät, että metodit ovat olioiden pääasiallinen tapa viestiä.
- Ymmärrät kapseloinnin periaateet ja hyödyt, ja miten olion sisäinen toteutus eroaa sen ulkoisesta käytöstä.
- Osaat toteuttaa ohjelman, jossa oliot toimivat yhdessä niin, että ne eivät ole riippuvaisia toistensa sisäisestä toteutuksesta.

Näkyvyysmääreet
Java tarjoaa kolme pääasiallista näkyvyysmäärettä: public, protected ja
private. Näkyvyysmääreet määrittelevät, mistä luokan jäseniin voidaan päästä
käsiksi.
Javassa oletuksena luokan jäsenet ovat ns. package-private-näkyvyydellä, mikä
tarkoittaa, että ne ovat näkyvissä vain samassa pakkauksessa oleville luokille.
Alla olevassa taulukossa on yhteenveto eri näkyvyysmääreiden vaikutuksista;
Oletus-rivi viittaa package-private-näkyvyyteen.
| Luokka | Pakkaus | Aliluokka | Muu maailma | |
|---|---|---|---|---|
public | Kyllä | Kyllä | Kyllä | Kyllä |
protected | Kyllä | Kyllä | Kyllä | Ei |
| oletus | Kyllä | Kyllä | Ei | Ei |
private | Kyllä | Ei | Ei | Ei |
Ensimmäinen sarake ("luokka") ilmaisee, onko luokan oliolla itsellään pääsy määritellyn näkyvyystason jäseneen. Kuten näet, oliolla on aina pääsy omiin jäseniinsä. Toinen sarake ("pakkaus") ilmaisee, onko muilla samassa pakkauksessa olevilla oliolla pääsy jäseneen. Kolmas sarake ("aliluokka") ilmaisee, onko luokasta perityillä aliluokan olioilla, jotka sijaitsevat pakkauksen ulkopuolella, pääsy jäseneen. Neljäs sarake ilmaisee, onko millä tahansa oliolla pääsy jäseneen.
Kun muut ohjelmoijat tai sinä itse käyttävät tekemääsi luokkaa, näkyvyysmääreet auttavat varmistamaan, että luokkaasi käytetään sillä tavalla, jolla olet suunnitellut sen käytettävän. Pääsääntö on, että ohjelmoijan tulisi käyttää mahdollisimman rajoittavaa näkyvyysmäärettä, ellei ole erityistä syytä käyttää jotain muuta. Tämä auttaa suojaamaan luokan sisäistä tilaa ja estämään tahalliset tai tahattomat väärinkäytökset luokan jäseniin. Erityisesti julkisten attribuuttien kohdalla tulisi pohtia tarkkaan, onko niille todella tarvetta, sillä ne altistavat luokan sisäisen tilan suoraan ulkopuolisille. Tällä opintojaksolla pyrimme suunnittelemaan ohjelmat niin, ettei julkisia attribuutteja -- poislukien vakiot -- tarvita.
huomautus
Tässä materiaalissa saatetaan hetkittäin käyttää esimerkinomaisesti julkisia attribuutteja. Tämä voi auttaa havainnollistamaan joitakin kohtia tiiviisti, mutta sitä ei suositella tuotantokoodissa.
Attribuutille tai metodille voi antaa näkyvyysmääreen lisäämällä sen esittelyriville. Luokalle voidaan myös asettaa näkyvyysmääre.
class Henkilo {
// Näkyvyysmääre 'private' piilottaa attribuutin niin, että sitä ei voi
// tarkastella luokan ulkopuolelta.
private String etunimi;
private String sukunimi;
// Näkyvyysmääre 'public' mahdollistaa metodin kutsumisen luokan ulkopuolelta.
public String annaMerkkijono() {
return etunimi + " " + sukunimi;
}
}
Kapselointi ja koheesio
Kapselointi (engl. encapsulation) on yksi keskeisimmistä käsitteistä olio-ohjelmoinnissa. Se tarkoittaa luokkien suunnittelua mahdollisimman itsenäiseksi ja modulaariseksi. Jokaisella luokalla on oma tehtävänsä, jota varten tarvittavat tiedot ja toiminnallisuudet kapseloidaan olion sisälle. Osa näistä tiedoista ja toiminnallisuuksista voidaan piilottaa vain olion sisäistä käyttöä varten. Olioiden tilan käsittelyä varten luokka tarjoaa käyttäjälleen tavallisesti julkisista metodeista koostuvan rajapinnan, mikä parantaa ohjelman muokattavuutta ja laajennettavuutta vähentämällä luokan sisäisistä muutoksista johtuvia sivuvaikutuksia.
Yhteen kuuluvan tiedon ja toiminnallisuuden sijoittaminen saman rakenteen sisälle on kapseloinnin ensimmäinen periaate. Olemmekin jo tehneet näin määritellessämme luokkia ja niille sopivia attribuutteja ja metodeja. Luokan koheesio (engl. cohesion) kuvastaa sitä, kuinka hyvin luokan attribuutit ja metodit sopivat yhteen luokan tarkoituksen kanssa. Luokkien suunnittelun tavoitteena on mahdollisimman korkea koheesio; luokan jäsenten tulisi olla sopivia sen tarkoitukseen.
Jos tekisimme esimerkiksi luokan kuvaamaan autoa, ei ehkä olisi kovin järkevää lisätä tähän luokkaan jäseneksi auton omistajan nimeä, osoitetta, puhelinnumeroa, jne. Omistajan tiedot ja niihin liittyvät toiminnallisuudet voi olla parempi laittaa omaan luokkaan.
Tehdään nyt luokka Auto, johon voimme soveltaa kapseloinnin periaatteita.
Lisätään aluksi vain attribuutit ja yksinkertaiset muodostajat.
class Auto {
String malli;
String valmistenumero;
double ajetutKilometrit;
public Auto(String malli, String valmistenumero, double ajetutKilometrit) {
this.malli = malli;
this.valmistenumero = valmistenumero;
this.ajetutKilometrit = ajetutKilometrit;
}
}
Haluaisimme tallentaa myös tietoja auton eri osista. Moottorista voisimme
tallentaa esimerkiksi mallin ja nykyisen kierrosluvun. Renkaista olisi hyvä
tietää ainakin malli, rengaspaine ja ehkä myös tyyppi, jolla voimme erottaa
kesä- ja talvirenkaat. Nämä voisi lisätä suoraan Auto-luokkaan
attribuuttina, mutta attribuuttien määrä kasvaisi aika suureksi, sillä
jokaisella renkaalla on omat tietonsa. Jos käyttäisimme taulukoita tai listoja,
tarvitsisimme niitäkin useita. Voi myös olla, että autossa ei välttämättä aina
ole moottoria kiinni. Renkaatkin on mahdollista ottaa irti tai niiden lukumäärä
voisi vaihdella automallien välillä.
Auton ei oikeastaan tarvitse olla tietoinen sen moottorin tai renkaiden sisäisestä toiminnasta, joten meidän on parempi tehdä useampi luokka, joista jokainen on vastuussa omista tiedoistaan ja toiminnoistaan.
Lisätään nyt Moottori ja Rengas -luokat ja määritellään näille sopivia
attribuutteja sekä muodostajat.
class Moottori {
String malli;
double kierrosluku;
public Moottori(String malli, double kierrosluku) {
this.malli = malli;
this.kierrosluku = kierrosluku;
}
}
Lisätään lopuksi moottori ja lista renkaista Auto-luokan attribuuteiksi. Nämä
sisältävät viitteet moottori- ja rengasolioihin. Muistetaan, että viitteet eivät
oletuksena osoita mihinkään, vaan meidän täytyy luoda myös oliot ja asettaa
viitteet osoittamaan niihin. Auto sisältää nyt muiden luokkien olioita, jotka
hoitavat omat vastuualueensa auton kokonaisuudessa. Tällaista rakennetta
kutsutaan kompositioksi (engl. composition). Yksi etu tässä on se, että auton
moottorin tai renkaat voisi vaihtaa helposti uusin asettamalla viiteattribuutit
osoittamaan uuteen olioon.
Lisätään myös pieni pääohjelma, jossa voimme luoda yhden auton oletusarvoilla ja tulostaa sen tietoja. Käytämme tässä vielä olion attribuuttien arvoja suoraan, mikä ei ole hyvän tavan mukaista. Teemme pian tämän paremmin.
class Moottori {
String malli;
double kierrosluku;
public Moottori(String malli, double kierrosluku) {
this.malli = malli;
this.kierrosluku = kierrosluku;
}
}
Kapseloinnin toinen periaate on luokan sisäisen tiedon piilottaminen ja sen käsittelyn rajoittaminen niin, että siihen päästään käsiksi vain tarkkaan määritetyn julkisen rajapinnan kautta. Oliolla voi olla sen tilan tallentamista varten paljon sisäistä tietoa, jonka ei ole tarkoitus olla tarkasteltavissa tai muokattavissa olion ulkopuolelta. Itse asiassa kaikki olion attribuutit tavallisesti piilotetaan, jotta oliota ei olisi mahdollista vahingossa saattaa attribuutteja suoraan muuttamalla virheelliseen tilaan. Oliolla voi myös olla sisäiseen käyttöön apumetodeja, joita ei ole tarkoitus voida kutsua ulkopuolelta.
Olion sisäisen tilan muokkaamista varten luokkaan määritellään julkisia metodeja, joita voidaan kutsua muualta ohjelmasta. Nämä metodit muodostavat edellä mainitun julkisen rajapinnan ja kaikki muutokset olion tilaan tapahtuvat niiden kautta, jolloin virheellisiin muutoksiin voidaan reagoida metodin sisällä sopivalla tavalla.
Se, mitä attribuutteja luokalla on tai miten niitä käsitellään luokan sisällä ovat yleensä toteutusyksityiskohtia, joita luokkaa käyttävän ohjelmoijan ei tarvitse tietää. Tällaisten toteutusyksityiskohtien piilottamisen tavoite on helpottaa ohjelmoijan työtä; kun luokan toteutusyksityiskohdat ovat piilotettuja ja tilaa käsitellään vain julkisen rajapinnan kautta, luokan sisäiseen toimintaan voidaan helpommin tehdä muutoksia niin, että luokan käyttäjä ei edes huomaa niiden tapahtuneen. Tämä on yksi kapseloinnin suurimmista höydyistä.
Lisätään Auto-luokalle muutama metodi yksinkertaista julkista rajapintaa
varten. Piilotetaan myös attribuutit, että emme voi muuttaa auton tilaa enää
suoraan. Luokan jäsenten näkyvyyttä luokan ja sen olioiden ulkopuolelle voidaan
muuttaa käyttämällä niiden esittelyn yhteydessä näkyvyysmääreitä kuten
public ja private. Moottori ja Rengas pysyvät vielä samana.
import java.util.ArrayList;
public class Auto {
private String malli;
private String valmistenumero;
private double ajetutKilometrit;
private Moottori moottori;
private ArrayList<Rengas> renkaat = new ArrayList<>();
public Auto(String malli, String valmistenumero, double ajetutKilometrit) {
this.malli = malli;
this.valmistenumero = valmistenumero;
this.ajetutKilometrit = ajetutKilometrit;
// Lisätään autolle moottori luomalla uusi moottori-olio.
moottori = new Moottori("M100", 0);
// Lisätään autolle neljä rengasta luomalla näille oliot ja lisäämällä
// viitteet renkaat-listaan.
renkaat.add(new Rengas("RR", "nastarengas", 100.0));
renkaat.add(new Rengas("RR", "nastarengas", 100.0));
renkaat.add(new Rengas("RR", "nastarengas", 100.0));
renkaat.add(new Rengas("RR", "nastarengas", 100.0));
}
public void aja(double kilometrit) {
if (kilometrit < 0) return;
this.ajetutKilometrit += kilometrit;
}
public void lisaaMoottori(Moottori moottori) {
this.moottori = moottori;
}
public void lisaaRengas(Rengas rengas) {
this.renkaat.add(rengas);
}
public void tulostaTiedot() {
IO.println("Malli: " + malli);
IO.println("Valmistenumero: " + valmistenumero);
IO.println("Ajetut kilometrit: " + ajetutKilometrit);
// Lisätään moottorin ja renkaiden tulostus seuraavaksi.
}
}
Piilotimme Auto-luokan attribuutit private-näkyvyymääritteellä ja auton
tilaa käsitellään nyt yksinkertaisten saantimetodien avulla. Siirsimme myös
tulostamisen luokan vastuulle. Meidän tulisi tehdä vielä samanlaiset muutokset
Moottori ja Rengas -luokille, jotta voimme käyttää niiden julkisia
rajapintoja Auto-luokan sisällä.
import java.util.ArrayList;
public class Auto {
private String malli;
private String valmistenumero;
private double ajetutKilometrit;
private Moottori moottori;
private final int maxRenkaat = 4;
private ArrayList<Rengas> renkaat = new ArrayList<>();
public Auto(String malli, String valmistenumero, double ajetutKilometrit) {
this.malli = malli;
this.valmistenumero = valmistenumero;
this.ajetutKilometrit = ajetutKilometrit;
}
public void aja(double kilometrit) {
if (kilometrit < 0) return;
this.ajetutKilometrit += kilometrit;
}
public void lisaaMoottori(Moottori moottori) {
this.moottori = moottori;
}
public void lisaaRengas(Rengas rengas) {
if (renkaat.size() < maxRenkaat) {
this.renkaat.add(rengas);
}
}
public void tulostaTiedot() {
IO.println("Auton malli: " + malli);
IO.println("Auton valmistenumero: " + valmistenumero);
IO.println("Ajetut kilometrit: " + ajetutKilometrit);
// Välitetään tulostuskomento moottorille, joka tulostaa itse omat tietonsa.
IO.println();
IO.println("Moottori:");
IO.println();
if (moottori != null) {
moottori.tulostaTiedot();
}
// Välitetään tulostuskomento myös jokaiselle renkaalle.
IO.println();
IO.println("Renkaat:");
for (Rengas rengas : renkaat) {
IO.println();
rengas.tulostaTiedot();
}
}
}
Nyt Auto-luokkamme ei enää ole riippuvainen Moottori tai Rengas -luokkien
sisäisistä toteutusyksityiskohdista.
Vastuu olion tilasta kuuluu oliolle itselleen
Joissain tähän mennessä nähdyissä esimerkeissä attribuutteja piilotettiin, mutta niitä voitiin edelleen muokata lähes suoraan metodin kautta. Tämä ei ole täysin olio-ohjelmoinnin tavoitteiden mukaista; julkisen rajapinnan ei ole tarkoitus olla vain väliaskel attribuutin muuttamisessa. Julkisen rajapinnan idea on välittää oliolle käsky tehdä jotain, jolloin olio suorittaa käskyn itse parhaaksi näkemällään tavalla.
Hyvä esimerkki tästä voisi olla esimerkiksi pelihahmo, jolla on sijaintia varten
attribuutteina koordinaatit X ja Y. Sen sijaan, että muuttaisimme pelihahmon
sijaintia suoraan yksinkertaisilla setX tai setY -metodeilla, voisimme
määritellä hahmolle liiku-metodin, joka ottaa tavoitekoordinaatit vastaan ja
sallii pelihahmon itse suorittaa tavoitekoordinaatteihin liikkumisen oman
sisäisen toteutuksensa ja rajoitteidensa mukaisesti. Tällöin vastuu
liikkumisesta kuuluu pelihahmolle itselleen. Jos pelihahmo ei esimerkiksi
kykenisi sillä hetkellä liikkumaan, metodi voi käsitellä tilanteen itse, jolloin
metodin kutsujan ei tarvitse ottaa tällaisia erikoistilanteita huomioon.
Todellisuudessa yksinkertaisia saantimetodeja käytetään usein myös tuotantokoodissa, sillä olioiden hyvä suunnittelu vie paljon aikaa ja vaivaa. Joskus voi olla myös ihan perusteltua muuttaa yksinkertaisen attribuutin arvoa suoraan metodin kautta. Metodi tuntuu tässä tapauksessa aika turhalta, mutta sen olemassaolo kuitenkin mahdollistaa sen, että luokan sisäistä toteutusta voidaan muuttaa ilman, että luokkaa käyttävä ohjelmakoodi hajoaa.
Tällä kurssilla emme valitettavasti ehdi käydä oliosuunnittelun teoriaa perusteellisesti läpi. Suosittelemme olio-ohjelmoinnin teorian oppimiseen tämän osan alussa mainittua kurssia.
Olioiden yhteistoiminta
Kerrataan vielä esimerkin avulla olioiden ja niiden yhteistyön suunnittelua tässä osassa opittuja käsitteitä käyttäen. Olioiden hyödyt tulevat paremmin esille, kun alamme rakentamaan ohjelmaan useampia luokkia, jotka tekevät yhteistyötä. Nyt kun olemme myös oppineet kapseloinnin periaatteista, voimme käyttää niitä organisoimaan ohjelmakoodia fiksummin.
Oliot voivat toimia yhdessä eri tavoin. Oliot voivat esimerkiksi sisältää toisia olioita - tai tarkemmin ilmaistuna viitteitä toisiin olioihin. Kun olio koostuu olioista, joista jokainen tuo oman toiminnallisuutensa, tätä kutsutaan usein kompositioksi. Oliot voivat kutsua toistensa metodeja ja näin delegoida tehtäviä toiselle oliolle, jolle tehtävän vastuu kuuluu, tai kommunikoida esimerkiksi tapahtumien yhteydessä. Oliot voivat myös sisältää kokoelmia olioista; esimerkiksi Javan sisäänrakennetut tietorakenteet ovat olioita, jotka sisältävät kokoelman olioista.
Katsotaan esimerkkiä, jossa haluamme mallintaa ohjelmallamme rakennuksia, niissä sijaitsevia tiloja sekä tiloihin tehtäviä varauksia. Jokaisessa rakennuksessa voi olla monta tilaa ja jokaisessa tilassa voi olla monta varausta. Emme välitä vielä tässä esimerkissä siitä, voiko varauksia olla samassa tilassa päällekkäin. Pidetään myös luokat vielä suhteellisen yksinkertaisina.
Aloitetaan määrittelemällä tarvittavat luokat Rakennus, Tila ja Varaus.
Tehdään näille myös muodostajat, jotka alustavat olion heti käyttökelpoiseen
tilaan.
import java.util.*;
public class Rakennus {
private String nimi;
private List<Tila> tilat = new ArrayList<>();
public Rakennus(String nimi) {
this.nimi = nimi;
}
}
Lisätään nyt rakennukselle sopivat metodit tilojen lisäämiseen. Haluaisimme löytää oikean tilan myöhemmin sen nimen perusteella, joten rakennuksessa ei saisi olla saman nimisiä tiloja. Tarvitsemme siis myös keinon hakea tila sen nimen perusteella. Tilan lisäämiseen tarvitsemme vain tilan nimen.
import java.util.*;
public class Rakennus {
private String nimi;
private List<Tila> tilat = new ArrayList<>();
public Rakennus(String nimi) {
this.nimi = nimi;
}
public Tila haeTila(String tilanNimi) {
for (Tila tila : tilat) {
if (tila.getNimi().equals(tilanNimi)) {
return tila;
}
}
return null;
}
public void lisaaTila(String tilanNimi) {
Tila tila = haeTila(tilanNimi);
if (tila != null) {
IO.println("Rakennus " + nimi + " sisältää jo tilan " + tilanNimi);
return;
}
tilat.add(new Tila(tilanNimi));
}
}
Tilaan pitäisi voida tehdä varauksia. Lisätään tämä ominaisuus, mutta pidetään esimerkki yksinkertaisena tekemällä varaukset tasatunneille. Ohjelman tila voisi kuvastaa esimerkiksi seuraavan päivän tilavarauksia.
Tarvitsemme varauksen tekemiseen varaajan nimen sekä varauksen alkamistunnin ja
keston. Lisätään oheinen metodi Tila-luokkaan.
public void lisaaVaraus(String varaaja, int ajankohta, int kesto) {
varaukset.add(new Varaus(varaaja, ajankohta, kesto));
}
Lisätään vielä mahdollisuus lisätä varauksia rakennuksen kautta niin, että
käyttäjän ei tarvitse edes tietää, että Tila-luokka on olemassa. Se miten
Rakennus tallentaa tiedot tiloista jää sen toteutusyksityiskohdaksi.
Tarvitsemme varauksen lisäämiseen tilan ja varaajan nimet sekä varauksen alkamistunnin ja keston.
Lisätään myös yksinkertaiset tulosta-metodit kaikkiin luokkiin, jotta voimme
nähdä kaikki rakennuksen tilat ja varaukset helposti.
import java.util.*;
public class Rakennus {
private String nimi;
private List<Tila> tilat = new ArrayList<>();
public Rakennus(String nimi) {
this.nimi = nimi;
}
public Tila haeTila(String tilanNimi) {
for (Tila tila : tilat) {
if (tila.getNimi().equals(tilanNimi)) {
return tila;
}
}
return null;
}
public void lisaaTila(String tilanNimi) {
Tila tila = haeTila(tilanNimi);
if (tila != null) {
IO.println("Rakennus '" + nimi + "' sisältää jo tilan '" + tilanNimi + "'");
return;
}
tilat.add(new Tila(tilanNimi));
}
public void lisaaVaraus(String tilanNimi, String varaaja, int ajankohta, int kesto) {
Tila tila = haeTila(tilanNimi);
if (tila == null) {
IO.println("Rakennuksessa '" + nimi + "' ei ole tilaa '" + tilanNimi + "'");
return;
}
tila.lisaaVaraus(varaaja, ajankohta, kesto);
}
public void tulosta() {
IO.println(nimi);
for (Tila tila : tilat) {
tila.tulosta();
}
}
}
Voimme nyt käyttää Rakennus-luokkaa niin, että meidän ei tarvitse olla
tietoisia tilojen tai varausten toiminnasta. Vastaavasti Rakennus ei riipu
suoraan siitä, miten Tila tallentaa varausten tietoja.
Tässä vaiheessa meiltä puuttuu vielä osa olio-ohjelmoinnin tärkeimmistä työkaluista; perintä, polymorfismi ja rajapinnat. Tutustumme näihin seuraavassa osassa.
Tehtävät
Toteuta luokka Ovi, joka mallintaa ovea, joka voi olla joko lukossa tai auki.
Attribuutit:
private boolean lukossaprivate String avainkoodi
Muodostaja saa oven avainkoodin parametrina: Ovi(String avainkoodi)
Muodostajan pitää asettaa avainkoodi ja asettaa ovi aluksi lukkoon.
Metodit:
boolean avaa(String koodi): avaa oven vain, jos koodi on oikein ja ovi on lukossa. Palauttaatrue, jos ovi avattiin, muutenfalse.boolean lukitse(): lukitsee oven vain, jos se on auki. Palauttaatrue, jos lukitseminen onnistui, muutenfalse.boolean vaihdaKoodi(String vanha, String uusi): vaihtaa avainkoodin uuteen, jos vanha koodi on oikein ja ovi on auki. Uusi koodi ei voi olla tyhjä merkkijono. Palauttaatrue, jos vaihto onnistui, muutenfalse.String tila(): palauttaa merkkijonon "Ovi on lukossa" tai "Ovi on auki"
Vain tila() saa tulostaa jotain. Muut metodit eivät.
Kirjoita pääohjelma, jossa
- luot oven
- lukitset oven
- yrität avata ovea väärällä koodilla
- avaat oven oikealla koodilla
- yrität avata jo avointa ovea
- yrität lukita jo lukittua ovea
- yrität vaihtaa koodia kun ovi on lukossa
- vaihdat koodin väärällä vanhalla koodilla
- vaihdat koodin oikealla vanhalla koodilla
- tulostat oven tilan
Toteuta luokka Saastolipas, jonka tarkoituksena on säilyttää rahaa.
Attribuutit:
private double saldo: Säästölippaan nykyinen rahamäärä.private String omistaja: Lippaan omistajan nimi.private final String SALASANA: Salasana, joka tarvitaan rahojen nostamiseen.
Konstruktori: Ottaa vastaan omistaja ja SALASANA -arvot. Asettaa
alkusaldoksi 0.0.
Metodit:
public void talleta(double maara): Lisää rahaa vain, jos maara on positiivinen.public double nosta(double maara, String annettuSalasana): Tarkistaa, onko annettuSalasana oikein. Tarkistaa, onko lippaassa tarpeeksi rahaa. Jos molemmat täyttyvät, vähentää saldon ja palauttaa nostetun määrän. Muuten palauttaa 0.0 ja tulostaa virheilmoituksen.public void tulostaSaldo(): Tulostaa "Hei <omistaja>, lippaasi saldo on <saldo> euroa.".
Korvaa kulmasulkeissa olevat kohdat sopivilla attribuuttien / parametrien arvoilla.
Tee pääohjelma, jossa luot Saastolipas-olion ja testaat sen toiminnallisuutta
eri tilanteissa, kuten tallettaminen, onnistunut nosto ja epäonnistuneet nostot
(väärä salasana, liian suuri nostettava määrä).
Tässä tehtävässä rakennat järjestelmän, joka valvoo rakennusten sähkönkulutusta ja estää sulakkeiden palamisen. Tehtävä koostuu vaiheista.
Vaihe 1: Sähkölaite
Tee luokka Sahkolaite. Laitteilla on kaksi muuttumatonta ominaisuutta: nimi ja
virrankulutus ampeereina.
Lisää attribuutit: private final String NIMI ja private final double VIRTA.
Oletetaan, että virrankulutus on aina positiivinen luku.
Tee konstruktori, joka asettaa nämä arvot.
Lisää private boolean kytketty, joka kertoo, onko laite päällä.
Tee metodit kytke() ja irrota(), jotka muuttavat kytketty-muuttujan tilaa.
Tee myös getVirta()-metodi, joka palauttaa laitteen virrankulutuksen,
vastaavasti getNimi().
Kokeile luokkaasi pääohjelmassa luomalla muutama laite ja kytkemällä niitä päälle ja pois.
Vaihe 2: Keskus ja oliolista
Luo Sahkokeskus-luokka. Tämän luokan tarkoituksena on hallita sähkölaitteita.
Lisää luokkaan attribuutti final double SULAKKEEN_KOKO. Sulakkeen koko voi
olla esimerkiksi 16 ampeeria tai 35 ampeeria. Lisää myös private boolean sulakePaalla, joka kertoo, onko sulake ehjä (true) vai palanut (false). Aluksi
sulake on päällä.
Luo List<Sahkolaite> paallaOlevatLaitteet (käytä ArrayListia).
Tee metodi double laskeNykyinenVirta(), joka käy listan läpi ja laskee laitteiden VIRTA-arvojen summan.
Vaihe 3: Valvova logiikka ja tilan hallinta
Keskuksen on päätettävä, saako laitteen kytkeä päälle.
Tee metodi boolean kytke(Sahkolaite laite). Metodin tulee tarkistaa, onko nykyinen virta + uuden laitteen virta <= sulakekoko. Jos on, laite lisätään listaan ja sille kutsutaan laite.kytke(). Muussa tapauksessa sulake palaa: aseta sulakePaalla = false, sammuta kaikki listan laitteet (irrota()) ja tyhjennä päälläolevien laitteiden lista.
Tee myös metodi void irrota(Sahkolaite laite), joka poistaa laitteen listasta ja kutsuu laite.irrota().
Vaihe 4: Globaali seuranta
Sähköyhtiö haluaa seurata kaikkien keskusten tilannetta.
Lisää Sahkokeskus-luokkaan static double kokonaisKulutusValtakunnassa.
Päivitä tätä muuttujaa aina, kun jokin laite missä tahansa keskuksessa kytketään päälle, irrotetaan sähkökeskuksesta tai kun sulake palaa.
Lisää static-metodi tulostaValtakunnanTilanne(), joka tulostaa kokonaiskulutuksen.
Voit testata ohjelmaasi TIMissä olevalla valmiilla pääohjelmalla, tai voit kirjoittaa oman testiohjelmasi.
Muokkaa esimerkin Rakennus, Tila ja Varaus -luokat sisältävää ohjelmaa
niin, että ohjelma ei anna lisätä samaan tilaan päällekkäisiä varauksia. Jos
tilassa on jo varaus, joka olisi päällekkäin uuden varauksen kanssa, uutta
varausta ei luoda.
Lisää myös tarkistukset, jotka estävät virheellisten varausten luomisen. Varauksen keston täytyy olla vähintään 1 tunti ja alkuajankohdan täytyy olla välillä 0-23.
Virhetilanteet voi tässä tehtävässä käsitellä tulostamalla virheilmoituksen.
Ennen tehtävän aloittamista kannattaa miettiä hetki, mitkä vastuut kuuluvat millekin oliolle.
Voit testata ohjelman toimintaa valmiiksi annetulla pääohjelmalla.