Opettelin tuossa vähän aikaan sitten luokkien tekoa ja niiden olioiden. Kuitenkin en ymmärtänyt mitä ihmeen hyötyä niistä on? Onko ne tehty vain koodin selvennyksen vuoksi vai kuinka? Voiko niillä tehdä jotain helpommin tai jotain erityistä jota itse C:llä ei voi tehdä niin helposti?
Myös kysyisin miksi kaikkea ei kannata tehdä public tyyppiseksi?
Luokat selkeyttää ainakin vieraiden kirjastojen käyttöä. Ja helpottaa omaa kodausta kun luokkissa on niiden omat funktiot.
Ja koko private juttu minä en ymmärrä yhtään. Mitä ihmeen hyötyä siitä on? Ehkä jos luokassa on muuttuja tai funktio jonka sorkkiminen aiheuttaa maailmanlopun siitä saattaa olla hieman hyötyä.
No olio-ohjelmointi liittyy vähän laajempaan kokonaisuuteen, kapselointiin.
Ideanahan on funktioiden, muuttujien ja kumppaneiden kokoaminen. Tähän sitten liittyy mm. tuon piilottaminen asiakkailta, eli käyttäjiltä. Tarkoituksena on osa ohjelmasta pitää ikäänkuin itsenäisenä muista riippumattomana osana. Ohjelmasta ja sen osista saadaan näin monikäyttöisempiä ja helpompaa kierrättää. Voi lisätä jonkun olion softaansa, josta ei tiedä mitään ja jonka toimintaperiaatteesta ei ole hajuakaan, ja kun se on piilotettu muilta, ei vahingossakaan sekoita siinä olevia muuttujia tai funktioita muiden kanssa. Ja tämähän sopii lähinnä laajempiin ohjelmistokokonaisuuksiin kuin muutamaan koodivinkkiin tai pieneen demoon :)
ts. jaetaan ohjelma pieniin helpommin hallittaviin osiin, jolloin ylläpitö helpottuu ja ohjelmaa/sen osasta on mahdollista kierrättääkin.
http://cpp.mureakuha.com/cppohje/cpp02.htm - Tossa on C++:lle jotain olio-ohjelmoinnista ja luokista.
Luulen tietäväni ja kuvittelen opiskelleeni eri lähteistä, että luokkien ja olioden hyödyt ovat sitä ilmeisempiä, mitä suurempia ja monimutkaisempia ohjelmistoja ollaan tekemässä ja mitä suuremmalla porukalla. Pikkujutut voi kuka tahansa tehdä itsekseen ihan miten lystää ja aina voi sanoa, että kyllä se ainakin minulla hienosti pelaa.
Kyllä C:llä voi tehdä kaiken, minkä esimerkiksi C++:llakin, mutta käsityötä on sitten enemmän. Oliokielet kuitenkin automatisoivat erilaisia tehtäviä ja mahdollistavat asioiden käsittelyn korkeammalla abstraktiotasolla. Varsinainen toiminta täytyy yleensä joka tapauksessa koodata jotenkin ihan kielestä riippumatta, mutta oliopuuha helpottaa suurempien ja monimutkaisempien kokonaisuuksien hallintaa. Vastaavasti koodaaminen on ainakin aloittelijalle vaikeampaa ja oikean luokkamallintamisen sisäistäminen vaatii opiskelua ja harjoittelua.
Yksi luokkiin liittyvä etu on, että ne tiettyyn rajaan saakka tukevat ihmisen tapaa ajatella ja luokitella asioita. Kielestä riippuen syntaksi tai notaatio voi auttaa koodin muotoilemisessa tavalla, joka on luonteva käsiteltävän ongelman kannalta. Joka tapauksessa, koodin jäsentelyn ja ymmärrettävyyden pitäisi parantua niin, että vahinkoja sattuu vähemmän ja ylläpito - siis muutosten tekeminen - on helpompaa.
Kapselointi ei sinänsä ole päämäärä vaan yksi keino, kun ohjelmakoodia kasaillaan järkeviksi komponenteiksi ja työkokonaisuuksiksi. Tiedon piilottaminen ei ole se varsinainen juju, vaan se, että tietoon voi päästä käsiksi vain tarkasti määrätyillä tavoilla, mikä ei ole ihan sama asia. Luokan rajapinta pitäisi ymmärtää eräänlaisena lupauksena siitä, mitä luokan oliot osaavat ja voivat tehdä. Oliot ovat vastuussa siitä, että nämä lupaukset pitävät ja että ne ovat ulospäin aina ehjiä ja kunnossa. Jos lupauksia ei voi pitää, tällaisesta poikkeustilanteesta pitää antaa olion käyttäjälle ilmoitus, jota ei voi noin vain sivuuttaa. Näin koodi, tehtävät ja vastuut jakautuvat luontevalla ja turvallisella tavalla, kun komponentit pysyvät ehjinä ilman, että kaikkien pitää koko ajan vahtia kaikkia. Varsinainen olio-ohjelmointi on suurelta osin luokkien vastuiden ja vuorovaikutusten suunnittelua.
Tuo private/public-pähkäily liittyy juurikin siihen että oliot voivat taata perinteisiä funktiokutsuja paremmin erilaisten esi- ja jälkiehtojen olevan aina voimassa. Kuvitellaanpa, että meillä on jokin ukkeli-olio, jota voidaan liikutella koordinaatistossa. C-tyylillä tai C++:aa ja public-muuttujia käyttäen tehdään tietenkin funktio, joka siirtää ukkelia eli ynnää sen koordinaatteihin siirroksen määrän. Speksit muuttuvat sitten kuitenkin niin, että ukkelin pitää pysyä aina koordinaatistossa rajatulla alueella. No, ei se mitään, lisätään siirtofunktioon tarkastus. Paha vain, että niitä erilaisia siirtymistapoja olikin jotain seitsemän, tulikos se tarkastus niihin kaikkiin? Entä naapuriosaston koodarihäiskä, joka kuljettaa ukkelia asettelemalla sen koordinaatteja suoraan itse, onkos hän edes kuullut mitään näistä tarkastusjutuista?
Jos homma tehdään oliomaisesti, ukkelin huolehdittavaksi annetaan minimaalinen määrä vastuita: syntyessään ukkelin pitää olla sallitulla alueella ja sama juttu minkä tahansa siirtymän jälkeen. Siis: ukkelin koordinaatit ovat sen oma private asia (kyllä ukkeli voi ne pyydettäessä kertoakin) ja ukkeli lupaa julkisesti (public) osata siirtyä (yksinkertainen siirtymäfunktio) annetun matkan. Ulkopuoliset voivat itse kehitellä tarvittavat tavat (apufunktiot), miten ukkelin pitää siirtyä (esmes nopeasti, hitaasti) mutta nämä kaikki muilutustavat käyttävät viime kädessä ukkelin omaa siirtymispalvelua. Vaikka speksit sitten muuttuisivatkin, homma ei leviä käsiin, sillä ukkeli ei vaan synny ruudun ulkopuolelle eikä sitä saa siirrettyä pois sallitulta alueelta. Ja se, joka tällaisen virheen yrittää tehdä, joutuu käsittelemään yrityksestä koituvan poikkeustilanteen.
Aika palikkaesimerkki, mutta periaate on ihan sama tosielämän mittakaavassakin. Tämä oliohomma ei ole ainoa tie autuuteen, kyllä esimerkiksi jollain funktionaalisella ohjelmointikielellä voi päästä samantyyppiseen lopputulokseen eri lähtökohdista ja eri keinoin.
Joku voi tykätä, että oliotouhu on suorituskyvyltään lähtökohtaisesti heikompaa. Kannattaa kuitenkin miettiä, haluaako verrata keskenään esimerkiksi automatisoitua virhetarkastusta ja resurssienhallintaa sisältävää C++-koodia C-koodiin, jossa virhetarkastuksia ei ole, tai C-koodiin, jossa kaikki virhetarkastukset ja resurssienhallinta on kirjoitettu käsin.
Tuon private:n voisi muuttaa sellaseksi että se on ulkopuolisille read-only. Sitten ei tarvittaisi sellaisia GetMuuttujaX-funktioita jotka varmaan ovat hitaampiakin kuin suora luku. Ja auttaa tilanteessa jossa sellaista funktioo ei ole eikä saa.
Lahha kirjoitti:
Sitten ei tarvittaisi sellaisia GetMuuttujaX-funktioita jotka varmaan ovat hitaampiakin kuin suora luku.
Eivät ole hitaampia, kun kirjoitetaan ne inline-funktioina ja käännetään koodi edes alkeellisimmalla optimoinnilla.
Lahha kirjoitti:
...GetMuuttujaX-funktioita jotka varmaan ovat hitaampiakin kuin suora luku. Ja auttaa tilanteessa jossa sellaista funktioo ei ole eikä saa.
Yleensä tossa "ei ole eikä saa" tapauksessa minkään ulkopuolisen ei ole tarkoituskaan päästä käsiksi private-dataan. Jos tollasia tilanteita tule C++ kanssa, niin kannattaa miettiä toiseen kertaan mitä on tekemässä väärin.
Yleensä kääntäjä osaa itsekin asettaa funktioita inline:ksi (optimoidessa), joten eipä noiden get-set -funkkareiden käyttämättömyyttä voi ainakaan tehonpuutteen syyksi laittaa. :)
Olio-ohjelmoinnin hyödyt käyvät selvemmiksi tutustuttaessa syvällisemmin C++:n ominaisuuksiin. Abstraktit luokat ovat luokkia, jotka määrittävät, mitä niistä periytettyjen luokkien olioiden on osattava tehdä. Abstraktilla luokalla (tai sen käyttäjällä / kääntäjällä) ei tarvitse olla aavistustakaan siitä, miten tämä tapahtuu. Lisäksi ohjelmoija voi käsitellä erilaisten luokkien olioita niiden yhteisten ominaisuuksien perusteella abstrakteina olioina.
class Kuvio { public: virtual void Piirra() = 0; };
Kaikki Kuviosta
periytettyjen luokkien oliot voidaan piirtää Piirra
-funktiolla. Toisin sanoen Kuvio
t (Viiva
, Ympyra
, BMPKuva
, ...) ovat sellaisia olioita, jotka voidaan piirtää. Itse Kuvio
-luokalle Piirra() funktion voi jättää määrittelemättä "= 0
" -merkinnällä ja näin tehdä luokasta abstraktin (kuinka piirtää "kuvio"?).
class Viiva : public Kuvio { private: float x1,x2,y1,y1; public: void Piirra() { /*piirtää viivan*/ } }; class Ympyra : public Kuvio { private: float x,y,r; public: void Piirra() { /*piirtää ympyrän*/ } }; class BMPKuva : public Kuvio { private: const char *tiedostonnimi; public: void Piirra() { /*piirtää BMP-kuvan tiedostosta*/ } }; // ...
void piirra_kuviot(Kuvio **kuviot, int maara) { for(int i=0; i<maara; i++) kuviot[i]->Piirra(); }
**(kuviot+i)
voi olla minkä tahansa Kuvio-luokasta periytetyn luokan olio (Ympyra, Viiva, BMPKuva, ...). Se, minkä luokan Pirra
-funktiota kunkin kuviolistan jäsenen kohdalla kutsutaan, selvitetään ajon aikana.
Vaikka virtuaalifunktioiden käyttö väärässä paikassa saattaa hidastaa ohjelmaa, voi niiden avulla saavutettava selkeys ja joustavuus helpottaa isompien projektien hallintaa huomattavasti.
Aihe on jo aika vanha, joten et voi enää vastata siihen.