Tieto vs kontrolli (struct- ja enum-tyypit)¶
Tässä kohdassa on tarkoitus puhua tieto-ohjatusta ohjelmoinnista. Sitä ennen esittelemme kuitenkin kaksi C++:n tietotyyppiä, joita asian selvittämiseksi tarvitaan.
Tietuetyyppi¶
Monissa ohjelmointikielissä ohjelmoijan on mahdollista määritellä omia tietotyyppejään, joiden avulla useita keskenään erityyppisiä tietoalkioita voidaan koota yhteen. Tavallaan kyseessä on eräänlainen alkeellisempi versio luokkatyypeistä: Mitään funktiorajapintaa ei muodostu, vaan osatietoja (kenttiä) käsitellään suoraan. Tällaista tietotyyppiä kutsutaan usein tietueeksi (record) tai C++:ssa struct:ksi.
C++:ssa tietue määritellään varatun sanan struct
avulla:
struct Tuote {
string tuotenimi;
double hinta;
};
Edellisen määrittelyn jälkeen uutta tyyppiä Tuote
voidaan käyttää hyvin
pitkälle samoin kuin muitakin tietotyyppejä:
Tuote tavara = {"saippua", 1.23};
Tuote
-tyyppisen muuttujan yksittäisiin osatietoihin päästään käsiksi
.
-operaattorilla:
tavara.hinta = 0.9 * tavara.hinta; // Alennus 10 %
cout << tavara.tuotenimi << ": " << tavara.hinta << endl;
tavara = { "appelsiini", 0.45 };
Tietuetta voi luonnollisesti käyttää myös STL-säiliöön talletettavana arvona, funktion parametrina (arvo- tai viiteparametrina) ja paluuarvona.
Luettelotyyppi¶
Joskus tulee tarve muuttujalle, jolla voi olla vain tiettyjä arvoja,
esim. kirjaston ohjelmistossa kirjan tila voi olla hyllyssä
, lainassa
,
varattu
tai vaikka hukassa
.
Olisi tietenkin mahdollista käyttää tähän tarkoitukseen string
- tai vaikka
int
-tyyppejä, mutta ne sallisivat paljon muitakin vaihtoehtoja.
Siksi ne eivät ole parhaita mahdollisia kirjan tilan tallentamiseen.
Luettelotyyppi on tietotyyppi, jonka kaikki mahdolliset arvot (alkiot) ovat ohjelmoijan määrittelemiä.
C++:ssa luettelotyyppi määritellään varatulla sanalla enum
:
enum Tyypin_nimi { alkio_1, ... , alkio_n };
Yllä määriteltiin uusi tyyppi Tyypin_nimi
, johon kuuluvat alkiot
alkio_1
, …, ja alkio_n
.
Konkreettisemmin:
enum Kirjantila { HYLLYSSA, LAINASSA, VARATTU, HUKASSA };
Luettelotyypin alkiot ovat kokonaislukuvakioita, joiden arvot C++-kääntäjä
määrittelee automaattisesti: alkio_1 = 0
, alkio_2 = 1
, jne.
Halutessaan ohjelmoija voi määrätä alkioiden arvot:
enum Tyypin_nimi { alkio_1 = arvo_1, ... , alkio_n = arvo_n };
Joskus on kätevää, että luettelotyyppi osaa kertoa oman kokonsa eli arvojensa lukumäärän:
enum Kuukausi { TAMMI, HELMI, MAALIS, HUHTI, TOUKO, KESA, HEINA,
ELO, SYYS, LOKA, MARRAS, JOULU, KUUKAUSIA_VUODESSA
};
Tieto-ohjattu ohjelmointi¶
Tietokoneohjelmat koostuvat tiedosta (data) ja niihin kohdistuvista käskyistä. Todellisuudessa tämä kahtiajako ei ole selvärajainen, vaan joissakin tapauksissa tieto voi korvata osan käskyistä tai päinvastoin. Tätä käytetään hyväksi nk. tieto-ohjatussa (data-driven / data-directed) suunnittelussa ja ohjelmoinnissa.
Tieto-ohjatun ohjelmoinnin idea on suunnitella ja toteuttaa ohjelmaan
tietorakenteita, joiden avulla tietoa käsittelevien käskyjen määrä
saadaan pienemmäksi, eli korvataan käskyjä tiedolla.
Tämä tulee paremmin esiin esimerkissä, jossa käytetään edellä esiteltyjä
struct
- ja enum
-tyyppejä.
(Esimerkin koodi löytyy kokonaisuudessan Gitistä:
examples/04/datadrivenprogramming).
Ajatellaan aluksi koodia, jossa ei ole hyödynnetty tieto-ohjatun ohjelmoinnin filosofiaa:
#include <string>
enum PostalAbbreviation {AL, AK, AZ, AR, CA, CO, ERROR_CODE}; // Excluded the rest 44 elements
// Version 1: First idea
PostalAbbreviation name_to_abbreviation(std::string name)
{
if(name == "Alabama"){
return AL;
} else if (name == "Alaska"){
return AK;
} else if (name == "Arizona"){
return AZ;
} else if (name == "Arkansas"){
return AR;
} else if (name == "California"){
return CA;
} else if (name == "Colorado"){
return CO;
} else { // Excluded 44 "else if" blocks
return ERROR_CODE;
}
}
Yllä oleva if
-lause olisi todellisuudessa paljon pidempi, siis jos myös
loppuja 44 osavaltiota käsittelevä koodi olisi kirjoitettu näkyviin.
Jos sama koodi kirjoitetaan soveltamalla tieto-ohjatun ohjelmoinnin
periaatteita, päästään pitkästä if
-lauseesta eroon seuraavasti:
#include <string>
#include <vector>
enum PostalAbbreviation {AL, AK, AZ, AR, CA, CO, ERROR_CODE}; // Excluded the rest 44 elements
struct StateInfo {
std::string name;
PostalAbbreviation abbreviation;
};
const std::vector<StateInfo> STATES = {
{ "Alabama", AL },
{ "Alaska", AK },
{ "Arizona", AZ },
{ "Arkansas", AR },
{ "California", CA },
{ "Colorado", CO } // Excluded 44 lines
};
// Version 2: Better solution
PostalAbbreviation name_to_abbreviation(std::string name)
{
for(auto s : STATES) {
if(name == s.name){
return s.abbreviation;
}
}
return ERROR_CODE;
}
Näiden kahden esimerkin ero on selvä:
Kun nähtiin hiukan vaivaa sopivan
tietorakenteen suunnitelussa ja alustamisessa, niin varsinainen
name_to_abbreviation
-funktio supistui minimaaliseksi.
Tieto-ohjatussa ohjelmoinnissa toteutustekniikkana on aina
(tavalla tai toisella) etukäteen prosessoitu informaatio, joka muodostuu
lähtöarvoista ja niistä seuraavasta lopputuloksesta.
Tieto-ohjatun ohjelmoinnin hyötyjä ovat:
- Syntyvät ohjelmat ovat yleensä lyhyempiä, mutta niiden selkeys ei silti kärsi.
- Virheiden mahdollisuus pienenee.
- Ohjelmien laajennettavuus ja ylläpidettävyys on helpompaa.
Varma merkki tieto-ohjatun uudelleensuunnittelun tarpeesta ohjelmassa on
liian pitkäksi kasvava if
-rakenne (tai switch
-rakenne, jota ei
ole tähän mennessä käsitelty).