Rajapinnat

Ohjelmistokomponenttien määrittäminen niiden käytöksen kautta mahdollistaa sen, ettei käyttäjän tarvitse tietää, miten komponentti on toteutettu. Toteutus voidaan kapseloida käyttäjälle suunnatun rajapinnan taakse. Rajapinnan tehtävä on kertoa käyttäjälle, miten komponentin on tarkoitus toimia. Samalla komponentilla voi olla tilanteesta riippuen erilaisia rajapintoja. Esimerkiksi olion rajapinta voi olla erilainen vakio-olioille, aliluokille ja muille peruskäyttötilanteille. Rajapinta on siis se osa komponenttia, jonka sen käyttäjä näkee ja jonka kautta komponenttia käytetään. Rajapinnan toteutus taas ei näy, eikä kuulu, käyttäjälle. Vastuujako rajapintaan ja sen taakse kapseloituun toteutukseen helpottaa sekä ohjelman rakenteen ymmärtämistä että toteutusta. Komponentin toteutus voidaan vaihtaa ilman, että sillä on vaikutusta komponenttia käyttävään osaan ohjelmistoa. Koska rajapinta on ainoa käyttäjälle näkyvä osa, siitä tulisi siis löytyä kaikki käyttäjän tarvitsema toiminnallisuus. Lisäksi käyttäjälle pitää tuoda selkeästi ilmi, miten rajapinnan tarjoamia palveluita tulee käyttää, mitä ne lupaavat ja miten ne käyttäytyvät myös virhetilanteissa.

Hyvä rajapinta on asia, jonka helpoiten tunnistaa, kun sellaisen näkee. Se on selkeä, hyvin dokumentoitu ja näin ollen helppo käyttää. Käyttäjähän näkee komponentista vain rajapinnan. Tämä korostaa rajapinnan dokumentoinnin merkitystä. Komponentille pitää siten dokumentoida mm. komponentin riippuvuudet, mahdolliset rajapinnan käyttöön liittyvät omistusvastuut sekä virhetilanteet ja niiden mahdollinen näkyminen käyttäjälle. Pelkkä rajapinnan ohjelmointikielinen määrittely ei siis riitä vaan tarvitaan dokumentointia, jotta käyttäjä osaa käyttää rajapintaa oikein.

Tiedon kätkentä komponentin rajapinnan taakse on yksi suurten ohjelmistojen toteuttamisen avainkäsitteitä. Se mahdollistaa ohjelmiston osien toteuttamisen erillään toisistaan. Se myös mahdollistaa koodikomponenttien uudelleenkäytön: kerran hyvin toteutettu komponentti on käytettävissä uudelleen. Näin ollen kommunikaatio ja se, miten eri ohjelmiston osat liittyvät toisiinsa, nousee keskeiseen rooliin. Toteutuksen rajapinnan taakse kapseloivien komponenttien suunnittelussa tämä tiivistyy ns. Parnasin periaateeseen (David Parnas 1972):

  • Suunnittelijan tulee antaa käyttäjälle ohjelmakomponentista kaikki tarvittava tieto, jotta komponenttia pystyy käyttämään tehokkaasti hyväkseen, muttei mitään muuta tietoa.

  • Suunnittelijalla tulee olla komponentista käytössään kaikki tarvittava tieto komponentille määrättyjen vastuiden toteuttamiseksi, muttei mitään muuta tietoa.

Rajapintojen suunnittelu ei ole vielä Ohjelmointi 3 -kurssilla keskiössä. Sen sijaan keskitymme käyttäjän näkökulmaan sekä toteuttajan näkökulmasta siihen, miten toteuttaa tietyt ehdot täyttävä rajapinta. Rajapintasuunnittelun peruslähtökohtia on miettiä:

  • Miten rajapintaa on lupa käyttää?

  • Mitä rajapinnan toiminnot lupaavat tehdä?

  • Millaisia virheitä toiminnoissa voi tulla?

  • Miten rajapintaa tulisi testata?

Rajapinnoista (kesto 9:01)

Rajapinta ohjelmointikielen rakenteissa

Ohjelmointikielitasolla rajapinta näkyy eri tavoin riippuen kielestä ja siitä, millaisesta komponentista on kyse. Tutuin rakenne lienee luokan julkinen ja yksityinen rajapinta. Luokan rajapinnassa olevat metodit määrittävät luokan julkisen rajapinnan, jonka läpi luokan objekteja käytetään. Lisäksi luokalla voi olla yksityinen rajapinta toteutuksensa apuna sekä yksityinen toteutus. Javassa on erikseen rakenne interface, jolla esitellään “tyhjä” rajapinta: joukko yhteenkuuluvaa toiminnallisuutta, jolle ei ole toteutusta irrallaan jostain luokasta. Rajapinnan toteutus sitten tehdään jossain luokassa. Palaamme tähän muutaman viikon päästä perusteellisemmin, kun olemme ehtineet oppia Javan luokista riittävästi.

Otetaan esimerkiksi päivämääräluokka C++:lla ja Javalla. Javassa on itsessään kattava pakkaus ajan ja päivämäärien käsittelyyn. (C++:ssa vastaava on std::chrono ). Osa metodien toteutuksista on esimerkistä jätetty Java-luokan kohdalla pois selkeyden vuoksi. Toisin kuin C++:ssa Javassa luokan toteutusta ei voida eriyttää rajapinnasta. Luokan rajapinta pelkkänä koodimäärittelynä ei ole suoraan kiinnostava käyttäjälle. Käyttäjä saa rajapinnasta tarvitsemansa tiedon dokumentaatiosta. Luokan ja sen julkisen rajapinnan dokumentaatio voidaan tuottaa suoraan luokan määrittelystä, kunhan se on kommentoitu oikein. Palaamme dokumentointiin, kunhan olemme saaneet toteutusosaamista tarpeeksi.

Olet oppinut C++:ssa määrittelemään julkisen rajapinnan luokkaan ensin. Tämä on tyylivalinta – koodikonventio. Javassa hyvänä tyylinä tyypillisesti pidetään, että luokan muuttujat määritetään ensin ja rajapinnan palvelut eli luokan jäsenfunktiot sen jälkeen. Tämä on järkevää luokan toteuttajan näkökulmasta. Käyttäjä suosinee muutenkin dokumentaatiota. Tutustumme Javan luokan määrittelyyn syvällisemmin seuraavaksi.

class Date
{
public:
    // Setting the date (note: no implementation given)
    void setDate(int d, int m, int y){}
    void setDate(int d, int m, int y);
    void setDay(int d);
    void setMonth(int m);
    void setYear(int y);

    int getDay() const;
    int getMonth() const;
    int getYear() const;

    // Advancing the date the given number of days
    void add(int n);

private:
    int day;
    int month;
    int year;
};
public class Date
{
    private int day_;
    private int month_;
    private int year_;

    // Setting the date (note: no implementation given)
    public void setDate(int d, int m, int y){}
    public void setDay(int d){}
    public void setMonth(int m){}
    public void setYear(int y){}

    public int getDay(){ return day_; }
    public int getMonth(){ return month_; }
    public int getYear(){ return year_; }

    // Advancing the date the given number of days
    public void add(int n){}
}

Hyvä rajapinta tarjoaa

Mieti edellä olevaa päivämääräluokkaa. Päätät vaihtaa luokan sisäisen toteutuksen käyttämään class Day {} sisäistä luokkaa (kts. seuraava osio Java-luokan perusrakenne). Muutos vaikuttaa:

Tutustu päivämääräluokan LocalDate rajapintaan ja siitä funktion compareTo-dokumentaatioon. Mihin funktiota voi käyttää?