• Ei tuloksia

Kenttien yksityisyydestä

Tämän luvun esimerkeissä kenttien nimet ovat aina alkaneet kahdella alaviivalla. Tä-mä on sen vuoksi, että tällaisella nimeämiskäytännöllä Pythonissa estetään se, että

luokan ulkopuolelta käytäisiin käsiksi suoraan kenttien arvoihin esimerkiksi seuraa-vaan tyyliin:

print a_vektori.__x_kerroin a_vektori.__y_kerroin = 3.0

Tämä ei ole siis mahdollista, vaan kaikessaTasovektori-luokan ulkopuolella olevas-sa koodisolevas-sa kenttien arvoja on käsiteltevä luokasolevas-sa määriteltyjen metodien kautta, esimerkiksi

print a_vektori.kerro_x_kerroin()

Ensiksi voi tuntua siltä, että on turhan hankalaa käsitellä kenttiä vain luokan meto-dien avulla. Eikö olisi paljon helpompaa, jos kenttiä voisi käsitellä luokan ulkopuo-lellakin suoraan?

Kenttien suoran käytön sulkeminen luokan sisälle saa kuitenkin aikaan sen, että luokan sisäistä esitystä voidaan myöhemmin muuttaa tarvittaessa helposti ilman että luokkaa käyttäviä ohjelmia tarvitsee muuttaa.

Tarkastellaan esimerkkinä tilannetta, jossa on määritelty luokka yhden henkilön kuvaamiseen. Henkilöllä on ainakin kentätnimija ika. (Luokka voi sisältää monia muitakin kenttiä, mutta ne eivät ole oleellisia esiteltävän asian kannalta.) Luokan määrittely siis alkaa seuraavasti:

class Henkilo1:

def __init__(self, nimi1):

self.nimi = nimi1 self.ika = 0

Luokkaa käytetään osana monia eri ohjelmia ohjelmassa käsiteltävien henkilöiden henkilötietojen esittämiseen. Esimerkiksi eräässä ohjelmassa voisi olla seuraavaa koo-dia (esitetyt käskyt ovat täysin mahdollisia, koska kenttiä nimija ika voi käsitellä vapaasti myös luokan ulkopuolella).

oppilas = Henkilo1("Matti") oppilas.ika = 15

if oppilas.ika < 18:

print "Oppilas on alaikainen"

else:

print "Oppilas on taysi-ikainen"

oppilas.ika = 16

print "Oppilaan ika on", oppilas.ika

Jossain vaiheessa eri ohjelmistojen ylläpitäjät tulevat siihen tulokseen, että on han-kalaa esittää eri henkilöiden ikiä vuosina, koska ikää pitää päivittää joka vuosi. Jos ohjelmistossa pidetään yllä tietoa tuhansista henkilöistä, tarkoittaa tämä tuhansien henkilötietojen päivittämistä vuosittain.

Niinpä päätetään muuttaa luokkaaHenkilo1siten, että henkilöstä esitetäänkin iän sijaan henkilön syntymävuosi. Luokan uuden määrittelyn alku olisi siis seuraava:

class Henkilo1:

def __init__(self, nimi1):

self.nimi = nimi1 self.syntymavuosi = 0

Tarkastellaan sitten, mitä muutoksia tämä vaatii edellä esitettyyn ohjelman osaan, jossa luodaanHenkilo-olio ja käsitellään tätä. Huomataan, että kaikki kohdat, joissa viitataan henkilönika-kenttään, pitää muuttaa. Muutokset eivät rajoitu pelkästään tähän ohjelmaan. Jos luokkaa on käytetty apuna monissa muissakin ohjelmissa, pitää kaikkiin näihin ohjelmiin tehdä muutoksia.

Tarkastellaan sitten vaihtoehtoista tapaa. Määritellään luokka Henkilo2. Tämä luokka on muuten samanlainen kuin alkuperäinen luokka Henkilo1, mutta luokan kentät on määritelty nyt niin, että niihin ei pääse käsiksi luokan suoraan luokan ulkopuolelta, vaan luokassa on määritelty omat metodit, joiden avulla kenttiä käsi-tellään. Luokan määrittelyn alku olisi seuraava:

class Henkilo2:

def __init__(self, nimi1):

self.__nimi = nimi1 self.__ika = 0

def kerro_ika(self):

return self.__ika

def muuta_ika(self, uusi_ika):

if 0 <= uusi_ika <= 150:

self.__ika = uusi_ika

Luokkaa käytettäisiin osana erilaisia ohjelmia henkilötietojen kuvaamiseen, esimer-kiksi seuraavasti:

oppilas = Henkilo2("Matti") oppilas.muuta_ika(15)

if oppilas.kerro_ika() < 18:

print "Oppilas on alaikainen"

else:

print "Oppilas on taysi-ikainen"

print "Oppilaan ika on", oppilas.kerro_ika()

Tarkastellaan sitten, mitä tapahtuu, jos henkilöstä päätetäänkin tallentaa iän sijasta syntymävuosi. LuokkaaHenkilo2ja siinä määriteltyjä metodeita pitää luonnollisesti muuttaa (nykyinen vuosi voitaisiin selvittää käytetyn vakion sijaan esimerkiksi tieto-koneen kellon ajasta, mutta koska tätä ei ole kurssilla opetettu, on vuosi määritelty vakion avulla):

NYKYINEN_VUOSI = 2009 class Henkilo2:

def __init__(self, nimi1):

self.__nimi = nimi1 self.__syntymavuosi = 0

def kerro_ika(self):

return NYKYINEN_VUOSI - self.__syntymavuosi

def muuta_ika(self, uusi_ika):

if 0 <= uusi_ika <= 150:

self.__syntymavuosi = NYKYINEN_VUOSI - uusi_ika

Tarkastellaan sitten, mitä pitää muuttaa aikaisemmassa ohjelmassa, joka luo Henkilo2-olion ja käsittelee sitä. Havaitaan, että tähän ohjelmaan ei tarvitse teh-dä lainkaan muutoksia. Vaikka luokan Henkilo2kenttiä on muutettu, niin luokan metodien toiminta ei ole luokan ulkopuolelta katsottuna muuttunut. Metodeita kut-sutaan edelleen samalla tavalla kuin aikaisemminkin ja ne paluttavat samanlaiset arvot kuin aikaisemminkin. Tämä on suuri etu, silläHenkilo2-olioita käyttäviä oh-jelmia saattaa olla lukuisia. Nyt niitä ei tarvitse muuttaa mitenkään, vaan pelkkä Henkilo2-luokan muuttaminen riittää.

Esimerkissä näkyy myös toinenkin etu siitä, että luokan kenttiä käsitellään vain luo-kan metodien kautta: Kentän arvoa muuttavaan metodiin on helppo lisätä tarkistus siitä, että kentälle ei yritetä antaa jotain kelvotonta arvoa, esimerkiksi negatiivista tai liian suurta ikää. Jos kentän arvoa muutetaan suoraan, pitää tämä tarkistus kir-joittaa erikseen jokaiseen ohjelman kohtaan, jossa kentän arvoa muutetaan tai sitten otetaan riski siitä, että jollakin kentällä voi olla ohjelmassa järjettömiä arvoja.

Vaikka kenttien nimien aloittaminen kahdella alaviivalla saakin aikaan sen, että kenttää ei voi käsitellä suoraan luokan ulkopuolelta, voi luokan sisällä olevissa metodeissa käsitellä suoraan kaikkien saman luokan olioiden kenttiä. Esimerkiksi Tasovektori-luokanpistetulo-metodin ensimmäinen versio oli kirjoitettu seuraa-vasti:

def pistetulo(self, toinen_vektori):

tulo = self.__x_kerroin * toinen_vektori.__x_kerroin + \ self.__y_kerroin * toinen_vektori.__y_kerroin return tulo

Tässä metodi käsittelee suoraan olion self kenttien lisäksi parametrina saadun olion toinen_vektori kenttiä. Tämä on täysin mahdollista, koska myös para-metrina saatu olio on Tasovektori-olio. Jos sen sijaan metodin parametrina oli-si eoli-simerkikoli-si Opiskelija-olio, ei Tasovektori-luokassa oleva metodi voisi käsi-tellä suoraan sen kenttiä __nimi, __opiskelijanumero, __harjoitusarvosana ja __tenttiarvosana.