Kapselointi ja julkinen rajapinta

Miksi kapseloidaan?

Terminä kapselointia (encapsulation) käytetään ohjelmoinnin yhteydessä kahdessa merkityksessä: tiedon ja käyttäytymisen sitomista yhteen yksikköön eli olioon sekä tiedon piilottamiseen olion sisäisin muuttujiin. Tällä kapseloinnilla tai piilottamisella pyritään estämään olion jäsenmuuttujien hallitsematon käyttö. Kapseloinnilla halutaan tilanne että olion jäsen muuttujiin ei pääse suoraan luokan ulkopuolelta käsiksi. Olion käyttäjän ei tarvitse tietää miten olio sisäisesti toimii, vaan riittää tietää että olion ulospäin näkyvä julkinen rajapinta kertoo miten oliota käytetään.

Mikä julkinen rajapinta?

Oliot voivat kutsua ja/tai käyttää toisia olioita vain julkisen rajapinnan eli ominaisuuksien ja julkisten metodien kautta. Kapselointi piilottaa olion/luokan sisäisen toteutuksen ja estää suojattujen osien käytön. Oheinen kuva selventää asiaa.


Olion ominaisuuksien asettaminen

Olion ominaisuuksia (properties) voi asettaa:
- olion luonnin yhteydessä konstruktoreille välitettävillä parametreilla
- olion ominaisuuksia asettamalla
- olion metodeja kutsuttaessa välitettävillä parametreilla
- olion jonkun tapahtuman (event) käsittelyn seurauksena (tähän palataan myöhemmin)

Demo Ominaisuudet

Seuraavassa koodiesimerkissä on esitelty miten ominaisuuksia (properties) määritellään eri tavoilla, ja kuinka niiden arvoa asetetaan riippuen onko ominaisuudella tausta jäsenmuuttujaa vai ei.


	


Ominaisuuksien asettaminen aksessoreilla

Olion ominaisuuksien asettamiseen voi ja kannattaa käyttää aksessoreita (acsessors). Aksessoreita on olemassa get- ja set-tyyppisiä:
check_circleget-aksessorilla palautetaan ominaisuuden arvo. get-aksessorin tulee päättyä aina return-lauseeseen, jossa palautetaan sitä vastaavan kentän arvo. Aksessorilla voidaan palauttaa taustamuuttujan arvo, tai palautettava arvo voidaan laskea.
check_circleset-aksessorilla asettaa ominaisuuden arvon. set-aksessori ei palauta mitään, mutta saa aina parametrina value-muuttujan, joka sisältää ominaisuuteen tallennettavan arvon. set-aksessorin yhteydessä voidaan tehdä tarkistuksia, jottei ominaisuudella aseta sellaista arvoa joka ei ole järkevä tai sallitu. Olion suojaamisen kannalta on tärkeää, että set-aksessorissa toteutetaan tarvittaessa tallennettavan arvon tarkistukset. Huom! Pääperiaate ja suositus on että set-aksessori ei heitä poikkeusta, vaikka arvon asettaminen ei onnistu. Lähtökohtaisesti pääohjelma voi luottaa siihen että ominaisuuden arvon asettaminen ei heitä poikkeusta. Jos haluat että ominaisuuden asettamisen pitää heittää poikkeus, käytä silloin luokan metodeja ominaisuuden asettamiseen.


	  
    

Metodi vai ominaisuus?

Olion ominaisuuksia ja sen tilaa voidaan muuttaa ominaisuuksien ja metodien avulla. Joskus joutuu valintatilanteeseen kumpaa kannattaisi käyttää. Pääperiaate on, että jos kyseessä on vain yksinkertainen olion ominaisuuden asettaminen johonkin arvoon, ilman erityisempiä toimenpiteitä, käytä ominaisuutta. Ominaisuuksien aksessoreilla voidaan tehdä yksinkertaisia tarkistuksia, että ominaisuuden arvo on järkevä tai sallituissa rajoissa. Sen sijaan, jos täytyy tehdä monipuolisempia tarkistuksia tai monipuolisempia muutoksia olion tilaan tai ominaisuuksiin, niin käytä metodia. Metodeilla voi myös välittää useamman parametrin. Lisäksi metodin paluuarvoa voi tietyissä tilanteissa käyttää hyväksi, kun taas ominaisuuden asettaminen ei palauta mitään. Myö se, että aksessorit eivät lähtökohtaisesti palauta poikkeusta, kannattaa huomioida. Jos pääohjelman pitää heittää poikkeus jostakin olion tilan muuttamisen tai toiminnon epäonnistumisesta, käytä metodia.
Oheisessa koodiesimerkissä raketin nopeus on vain luettavissa oleva ominaisuus, ja sitä voi muuttaa vain metodin Accelerate avulla. Metodi tarkistaa että nopeus ei nouse yli sallitun ja palauttaa true jos nopeutta nostettiin.


	

C#-kieleen liittyvä huomio

C#:ssa pyritään välttämään Get- ja Set-alkuisia metodeja, koska vastaava toiminto voidaan yleensä hoitaa ominaisuuksien aksessoreilla. Eli esimerkiksi jos huomaat kirjoittavasi metodit GetSalary() ja SetSalary(float salary), niin harkitse kannattaisiko tuossa tilanteessa käyttää ominaisuutta public float Salary {get; set;}