Лабораторная работа № 4
Тема: Интерфейс и реализация класса. Принцип организации модульности при построении объектно-ориентированных программ
Цель: познакомиться с принципом организации модульности и научиться реализовывать принцип инкапсуляции и абстрагирования посредством модульности программного кода.
Теоретические сведения
Клaсс (class) – это определенный пользователем тип данных, который используется для описания абстрактного множества объектов, связанных обобщением структуры и поведения. В определении нового типа основная идея - отделить несущественные подробности реализации (например, формат данных, которые используются для хранения объекта типа) от тех качеств, которые существенны для его правильного использования (например, полный список функций, которые имеют доступ к данным). Такое разделение можно описать так, что работа со структурой данных и внутренними административными подпрограммами осуществляется через специальный интерфейс (каналируется).
Поскольку класс описывает новый тип объекта, а создавать объекты этого класса можно столько, сколько нужно, то целесообразно вынести объявление класса в отдельный заголовочный файл, который можно будет использовать в нескольких программах.
Такие основные концепции ООП, как инкапсуляция, абстрагирование на техническом уровне достигается модульностью - разбиением программы на несколько файлов.
Вполне логично возникает вопрос: что именно поместить в один файл, что в другой?
Проще всего решить проблему разбиения программы на несколько файлов можно, поместив функции и определения данных в подходящее число исходных файлов (реализация класса) и описав типы, необходимые для их взаимодействия, в одном заголовочном файле, который включается во все остальные файлы (интерфейс класса). Взаимосвязь файлов реализуется путем подключения директивы #include.
Такие файлы позволяют добиться двух целей. Во-первых, гарантируется, что все исходные файлы содержат одно и то же объявление для глобального объекта или функции. Во-вторых, при необходимости изменить объявление это изменение делается в одном месте, что исключает возможность забыть внести правку в какой-то из исходных файлов. (Поскольку единицей компиляции является файл, то во всех случаях, когда в файл вносится изменение (сколь бы мало оно ни было), весь файл нужно компилировать заново. Даже для программы умеренных размеров время, затрачиваемое на перекомпиляцию, можно значительно снизить с помощью разбиения программы на файлы подходящих размеров.)
Все нетривиальные программы собираются из нескольких раздельно компилируемых единиц (их принято называть просто файлами).
Такой подход строиться исключительно на соглашениях и не явл. строгим требованием языка, хотя остается желаемым.
Рассматривая организацию данных Дата, мы замечаем, что объявление класса Дата целесообразно вынести в заголовочный файл Date .h.
#include<stdio.h>
#include<conio.h>
class Date
{
int d; // день
int m; // мiсяць
int y; // рiк
public:
void print();
Date(int dd,int mm,int yy);
int leapyear();
void add_day(int dd);
void add_month(int mm);
void add_year(int yy);
};
void Date::print()
/* виведення на екран дати */
{
printf("%d.%d.%d\n",d,m,y);
}
Date::Date(int dd,int mm,int yy)
/* iнiцiалiзацiя структури типу Date */
{
d=dd;
m=mm;
y=yy;
}
int Date::leapyear()
/* визначення, чи високосний рiк */
{
if ((y%4==0&&y%100!=0)||(y%400==0)) return 1;
else return 0;
}
void Date::add_year(int yy)
/* додати yy рокiв до дати */
{
y+=yy;
}
void Date::add_month(int mm)
/* додати mm мiсяцiв до дати */
{
m+=mm;
if (m>12)
{
y+=m/12;
m=m%12;
}
}
void Date::add_day(int dd)
/* додати dd днiв до дати */
{
int days[]={31,28,31,30,31,30,31,31,30,31,30,31};
d+=dd;
if (leapyear()) days[1]=29;
while ((d>days[m-1]))
{
if (leapyear()) days[1]=29;
else days[1]=28;
d-=days[m-1];
m++;
if (m>12)
{
y+=m/12;
m=m%12;
}
}
}
void main(void)
{
Date date1(14,02,2008),date2(1,1,2003), my_burthday (17,02,1985);
// мой день рождения
date1.print();
date2.print();
my_burthday.print();
date1.add_day(16);
date1.print();
date2.add_month(15);
date2.print();
date1.leapyear();}
При таком стиле использования заголовочных файлов .h файл и связанный с ним .c файл можно рассматривать как модуль, в котором .h файл задает интерфейс, а .c файл задает реализацию. Или вот фрагмент програмного кода:
Таблица символов не зависит от остальной части калькулятора за исключением использования функции ошибок. Это можно сделать явным:
//table.h: описания таблицы имен
struct name {
char* string;
name* next;
double value;
};
extern name* look(char* p, int ins = 0);
inline name* insert(char* s) { return look(s,1); }
//table.c: определения таблицы имен
#include "error.h"
#include
#include "table.h"
const TBLSZ = 23;
name* table[TBLSZ];
name* look(char* p; int ins) { /* ... */ }
Программа, состоящая из нескольких раздельно компилируемых файлов, должна быть согласованной в смысле использования имен и типов, точно так же, как и программа, состоящая из одного исходного файла.
Поэтому дальнейшее исследование модульности классов требует рассмотрение дополнительных моментов.
Любой глобальный объект, используемый в программе, должен быть определен, причем только один раз. Встроенные функции могут определяться несколько раз, если только все определения совпадают. Такое требование единственности или точного совпадения получило название правила одного определения (ПОО).