Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kapselointi

osaamistavoitteet

  • Tiedät, mitä näkyvyysmääreet kuten public ja private tarkoittavat.
  • 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.

Autoa ajetaan, vaikka emme tiedä miten moottori toimii

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.

LuokkaPakkausAliluokkaMuu maailma
publicKylläKylläKylläKyllä
protectedKylläKylläKylläEi
oletusKylläKylläEiEi
privateKylläEiEiEi

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.

Henkilo.java
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.

Auto.java
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.

Moottori.java
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.

Moottori.java
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.

Auto.java
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ä.

Auto.java
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.

Rakennus.java
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.

Rakennus.java
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.

Rakennus.java
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

Tehtävä 2.7: Ovi1 p.

Toteuta luokka Ovi, joka mallintaa ovea, joka voi olla joko lukossa tai auki.

Attribuutit:

  • private boolean lukossa
  • private 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. Palauttaa true, jos ovi avattiin, muuten false.
  • boolean lukitse(): lukitsee oven vain, jos se on auki. Palauttaa true, jos lukitseminen onnistui, muuten false.
  • 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. Palauttaa true, jos vaihto onnistui, muuten false.
  • 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

Tee tehtävä TIMissä
Tehtävä 2.8: Säästölipas1 p.

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ä).

Tee tehtävä TIMissä
Bonus: Tehtävä 2.9: Sähköverkko1 p.

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.

Tee tehtävä TIMissä
Bonus: Tehtävä 2.10: Varaukset1 p.

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.

Tee tehtävä TIMissä