Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Инкапсуляция




Сокрытие реализации структуры и операций называется инкапсуляцией. Разработав и реализовав единожды некий скрытый тип, можно больше никогда не возвращаться к этой задаче, а стать пользователем своего модуля, просто рассматривая его как «черный ящик».

Использование скрытых типов имеет следующие преимущества.

- Детальное описание структуры данных не загромождает абстракцию.

- Компоненты скрытого типа недоступны.

 

Скрытие структуры данных в разделе описания повышает абстракцию самого типа – разработчик не знает никаких деталей, для него тип представлен неким неделимым единым объектом с набором методов (процедур) для работы с ним.

С другой стороны, недоступность деталей и структуры скрытого типа не дает работать с ним в обход заранее заданных функций, т.к. не возможно непосредственное обращение к компонентам типа и работа с ними (если не предоставлены соответствующие функции). Такой подход в некотором смысле «защищает» структуры типа. Т.е. никакими действиями разработчик не может нарушить целостность типа.

В языке Си скрытый тип может быть описан только при помощи указателя. Название типа помещается в заголовочный файл для включения в модули - пользователи, а полное описание типа – в сам модуль (или его собственный h-файл). Конечно такая схема реализации не защищает переменную срытого типа от вмешательства, но при соблюдении определенных соглашений обеспечивает повышение уровня безопасности кода.

Важным преимуществом использования скрытых типов, кроме «защиты» от нарушения целостности, является возможность их модификации без изменения использующих скрытый тип программ.

Рассмотрим реализацию скрытого типа «человек» в отдельном модуле. Как уже говорилось, в Си скрытый тип можно реализовать только при помощи указателя. Работу с указателем можно упростить, определив нужную структуру и рассмотрев указатель на нее. В этом случае следует обеспечить корректное выделение памяти только в начале работы с указателем и освободить память по окончанию. Остальные операции не потребуют дополнительных манипуляций с памятью.

Чтобы задать тип в отдельном модуле надо создать «.h» файл и поместить в него определение типа и заголовки методов работы с типом. Пусть «человек» будет хранить информацию о возрасте (другие поля опустим для упрощения). Тогда «.h» файл может выглядеть следующим образом:

 

/* file: person.h */

struct PEROBJ;

typedef struct PEROBJ *PERSON;

 

void createPerson(PERSON *p);

void setAge(PERSON p, int newAge);

int getAge(PERSON p);

void deletePerson(PERSON *p);

 

Мы задали тип как PERSON, который является указателем на PEROBJ. Описание и структура PEROBJ не раскрывается, поэтому из заголовочного файла нелзя увидеть структуру типа, а значит из импортера не удастся работать с ней напрямую. Сразу определить тип как:

 

typedef struct PERSON;

 

нельзя, т.к. в импортере при определении переменной типа PERSON получится, что есть переменная неизвестного типа, что в Си запрещено. В приведенном выше примере эта ситуация обходится путем определения указателя на неизвестный тип. Тогда в импортере переменная типа PERSON будет указателем и никаких ошибок не возникает.

Для «человека» заданы операции создания и удаления. Они необходимы для выделения памяти под указатель и для ее высвобождения. Для всех скрытых типов приходится реализовывать такие операции. Более того, их необходимо обязательно использовать в импортере, иначе возникнет ошибка работы с указателем, для хранения значений которого не выделена память. К сожалению, большинство структурных языков программирования высокого уровня, и Си в том числе, не обладают никакими средствами помощи и контроля для создания/удаления переменных пользовательских типов. Это является одним из недостатков скрытых типов, который решается лишь в объектно-ориентированных языках.

Две другие приведенные операции – это операции селекторы: задание и считывание возраста. Они необходимы, т.к. доступа к внутренней структуре нет и единственный способ работы с возрастом – это специальные методы. В функцию задания возраста переменная p передается по значению, несмотря на то, что в ней надо изменить значение объекта p. Дело в том, что сама переменная р уже является указателем, а изменения происходят в значении, на которое она указывает. А вот в функции создания переменная р передана по ссылке, т.к. при выделении памяти надо изменить само значение указателя (ссылки) р на новую область памяти. [может надо картинку нарисовать?]

Реализация типа помещается в «.c» файл, который может иметь вид:

 

/* file: person.c */

 

#include "person.h"

#include <malloc.h>

 

struct PEROBJ {

int age;

};

 

 

void createPerson(PERSON *p) {

*p = malloc(sizeof(struct PEROBJ));

}

 

void setAge(PERSON p, int newAge) {

(*p).age = newAge;

}

 

int getAge(PERSON p) {

return (*p).age;

}

 

void deletePerson(PERSON *p) {

free(*p);

}

 

 

Здесь приводится описание типа PEROBJ с которым происходит реальная работа и реализации указанных в «.h» файле функций. Чтобы работать с полями PERSON приходится использовать конструкцию (*p).age, т.к. тип реализован указателем на структуру.

Для того, чтобы работать с реализованным модулем, надо его импортировать, определить переменную типа PERSON и не забыть вызывать функцию создания перед работой с этой переменной.

 

/* file: test.c */

 

#include <stdio.h>

#include "person.h"

 

int main() {

PERSON p1;

 

createPerson(&p1);

 

setAge(p1, 23);

printf("Hi, %d\n", getAge(p1));

 

deletePerson(&p1);

 

return 0;

}

 

Саму переменную age из импортирующего модуля изенить нельзя – она не доступна. Это позволяет сохранять целостность типа. В импортере нельзя написать

 

(*р1).age = -10;

 

и сделать переменную р1 некорректной.

В рассмотренном примере возраст хранится как количество лет. А что если понадобится знать дату рождения и уметь рассчитывать возраст в зависимости от текущего года? Здесь проявляется одно из важнейших свойств скрытого типа – простота модификации. Чтобы сохранить дату рождения – можно добавить новые поля – день месяца, месяц и год рождения. При этом поле age больше будет не нужно и даже вредно, т.к. может запутать. Новые изменения приведут к следующему коду.

В «.h» файл добавим два прототипа новых функций:

 

void setBirthDay(PERSON p, int day, int month, int year);

int getBirthDay(PERSON p);

 

В «.c» файле изменим реализацию setAge и getAge и добавим две новых функции:

 

#include "person.h"

#include <malloc.h>

#include <time.h>

 

struct PEROBJ {

int day; /* 1-31 */

int month; /* 1-12 */

int year; /*1800 — 2100 */

};

 

 

void createPerson(PERSON *p) {

*p = malloc(sizeof(struct PEROBJ));

}

 

void setAge(PERSON p, int newAge) {

/* get current date in C format */

time_t timer = time(NULL);

 

/* convert our date to structure */

struct tm *t = localtime(&timer);

 

/* set current day */

(*p).day = (*t).tm_mday;

 

/* set current month (C format:0-11, our is:1-12)*/

(*p).month = (*t).tm_mon+1;

 

/* set birth year */

/*(curent – newAge, */

/*C format for year is: years from 1900) */

(*p).year = (*t).tm_year+1900-newAge;

}

 

int getAge(PERSON p) {

 

/* get current date in C format */

time_t timer = time(NULL);

 

/* convert our date to structure */

struct tm *t = localtime(&timer);

 

return ((*t).tm_year+1900)-(*p).year;

}

 

void setBirthDay(PERSON p, int day, int month, int year) {

(*p).day = day;

(*p).month = month;

(*p).year = year;

}

 

int getBirthYear(PERSON p) {

return (*p).year;

}

 

void deletePerson(PERSON *p) {

free(*p);

}

 

Интерпретация переменной age изменена, а ранее реализованные методы setAge и getAge по прежнему возвращают возраст в годах. Все раннее написанные программы, которые использовали наш скрытый тип PERSON продолжают работать как и работали, никаих изменений в них не требуется. А все новые программы могут использовать новые методы setBirthDay и getBirthYear (по аналогии с которой можно написать функции получения месяца и дня рождения).

Прмер основной программы, работающей с новым модулем:

 

/* file: test2.c */

 

#include <stdio.h>

#include "person.h"

 

int main() {

 

PERSON p1;

 

createPerson(&p1);

 

setAge(p1, 23);

printf("Hi, %d\n", getAge(p1));

 

setBirthDay(p1, 23,05,1982);

printf("Hi, %d\n", getAge(p1));

 

printf("Hi, %d\n", getBirthYear(p1));

 

deletePerson(&p1);

}

 

Такая модификация стала возможной благодаря тому, что сама переменная age была скрыта от импортера и не могла использоваться. Сокрытие внутренней структуры привело не только к дополнительной защите от случайных изменений, но и обеспечило не использование напрямую переменной age ни в одном из импортирующих модулей. Это важнейшее свойство скрытого типа сильно упрощает модификацию его реализации впоследствии.

При реализации скрытых типов необходимо учитывать некоторые особенности.

Во-первых, как уже было сказано, импортирующий модуль не должен иметь непосредственного доступа к компонентам срытого типа, поэтому в реализации скрытого типа необходимо предусмотреть все необходимые для работы с типом операции – обеспечить полноту операций. Основные классы возможных операций над скрытым типом аналогичны операциям над абстрактным типом.

Во-вторых, т.к. скрытый тип описывается при помощи указателей, модуль, экспортирующий скрытый тип, в дополнение ко всем типам операций абстрактного типа, всегда должен иметь функции создания, уничтожения и копирования значения объекта.

В ходе выполнения операций над скрытыми типами возможны ситуации, при которых возникают различные ошибки (нехватка памяти, и т.д.) о которых необходимо сообщать импортирующему скрытый тип модулю. Важность корректного механизма обработки ошибок, кроме всего прочего, связана с невозможностью проверки результата выполнения функции, т.к. ни структура, ни данные скрытого типа напрямую не доступны.

Наиболее часто используемые механизмы сообщений об ошибках – это коды возврата функций, использование параметра функции для возврата кода/сообщения об ошибке или использование глобальной переменной модуля. Реже используется дополнительная функция, которая при вызове сообщает, была ли ошибка при выполнении предпоследней функции (последней выполнялась она сама). Это далеко не полный перечень подходов к реализации механизма сообщений об ошибках.

Стоит отметить, что модуль, реализующий скрытый тип, в большинстве случаев не должен выводить ошибки на экран (стандартное устройство вывода), чтобы не нарушить формат вывода, которым вероятнее всего управляет сама головная программа. Вывод на экран значения скрытого типа, возможно, покажется очевидной функцией модуля реализации этого скрытого типа, однако в ходе работы с модулем она практически не востребована.

Чаще всего головная программа самостоятельно управляет выводом и чтобы написать процедуру вывода значения переменной скрытого типа, надо учесть в ней все возможные особенности вывода (шрифт, цвет, положение, …), которые использует головная программа. Обычно, они заранее не известны.

С другой стороны, получение значений переменной скрытого типа в виде стандартных типов является удобной функцией, позволяющей в головном модуле написать свою функцию вывода. Здесь надо учитывать, что возврат значения в конечном итоге должен осуществляться в переменные стандартных типов и это преобразование должно обеспечить «полноту» результата. Т.е., если скрытый тип – это список студенческой группы, то функция, возвращающая в головной модуль значение переменного такого типа должна выдать всю имеющуюся информацию о каждом студенте.

 





Поделиться с друзьями:


Дата добавления: 2014-12-26; Просмотров: 370; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.053 сек.