Toinen projekti
Modulaarisuus
Modulaarisuus on ohjelman suunnittelun ja toteutuksen tapa jakaa suuri ohjelma pienempiin, helpommin hallittaviin osiin. Moduulit ovat selkeitä kokonaisuuksia, jotka toimivat yhdessä muodostaen koko ohjelman.
Moduuli koostuu toisiinsa liittyvistä ohjelmarakenteista. Useimmissa ohjelmointikielissä moduuli toteutetaan omassa lähdekooditiedostossaan tai tiedostoparissaan. Moduuleissa on tyypillisesti julkinen rajapinta, jonka kautta muut osat käyttävät sen tarjoamia palveluita, sekä yksityinen toteutus.
Moduulit ja luokat eivät ole sama asia. Niitä yhdistää rajapintojen käyttö, ja usein luokka, joka muodostaa selkeän osan ohjelmasta, kannattaa sijoittaa omaan moduuliinsa.
Esimerkki: geometrialaskin
Tarkastellaan pientä geometrialaskinta, joka laskee neliön ja ympyrän ympärysmittoja ja pinta-aloja. Ohjelma jakautuu pääohjelmamoduuliin (calculator.cpp) ja laskutoimitukset tarjoavaan geometry-moduuliin.
Pääohjelma ottaa käyttöön geometriamoduulin palvelut sisällyttämällä sen otsikkotiedoston:
// calculator.cpp
#include "geometry.hh"
#include <iostream>
using namespace std;
int main() {
double dimension = 0.0;
cout << "Input the length of the side of a square: ";
cin >> dimension;
cout << "Perimeter: " << square_perimeter(dimension) << endl
<< "Area: " << square_area(dimension) << endl;
cout << "Input the radius of a circle: ";
cin >> dimension;
cout << "Perimeter: " << circle_perimeter(dimension) << endl
<< "Area: " << circle_area(dimension) << endl;
}
Julkinen rajapinta otsikkotiedostossa
C++-moduuli, joka tarjoaa palveluita muille moduuleille, kuvaa ne esittelytiedostossa eli otsikkotiedostossa. Tiedoston pääte on kurssilla yleensä .hh. Geometriamoduulin otsikko sisältää pääohjelman tarvitsemien funktioiden esittelyt:
// geometry.hh
#ifndef GEOMETRY_HH
#define GEOMETRY_HH
double square_perimeter(double side);
double square_area(double side);
double circle_perimeter(double radius);
double circle_area(double radius);
#endif // GEOMETRY_HH
Esittelyt kertovat, mitä muille moduuleille tarjotaan, mutta eivät sitä, miten palvelut toteutetaan. Siksi pääohjelma voi kutsua näitä funktioita, vaikka niiden määrittelyt ovat toisessa tiedostossa.
Rivi #include "geometry.hh" on esiprosessorin direktiivi. Esiprosessori valmistelee lähdekoodin ennen kääntämistä ja liittää otsikkotiedoston sisällön sitä käyttävään lähdekooditiedostoon. Otsikkotiedostoon ei lisätä .cpp-tiedostoja.
Sama otsikkotiedosto saattaa tulla mukaan useamman kerran eri sisällytysketjujen kautta. Include guard -direktiivit #ifndef, #define ja #endif varmistavat, että tiedoston sisältö käsitellään vain kerran. Käytä tunnisteena otsikkotiedoston nimeä isoilla kirjaimilla ja muuta piste alaviivaksi, kuten esimerkissä GEOMETRY_HH.
Pääohjelmamoduulille ei yleensä tarvita omaa esittelytiedostoa, koska muut moduulit eivät käytä sen tarjoamia palveluita.
Yksityinen toteutus cpp-tiedostossa
geometry.cpp sisältää julkisesti esiteltyjen funktioiden määrittelyt ja niiden tarvitsemat apupalvelut. Esimerkiksi:
// geometry.cpp
const double PI = 3.141593;
double square_perimeter(double side) {
return 4 * side;
}
double square_area(double side) {
return side * side;
}
double circle_perimeter(double radius) {
return 2 * PI * radius;
}
double circle_area(double radius) {
return PI * radius * radius;
}
Geometriamoduulin käyttäjän ei tarvitse tietää, miten funktiot laskevat tulokset. Toteutus voidaan tarvittaessa vaihtaa, kunhan julkinen rajapinta pysyy samana.
Seuraavalla sivulla tarkastellaan, miten useasta moduulista koostuva ohjelma käännetään ja miten moduulijako suunnitellaan suuremmassa esimerkissä.