Опубликован: 05.11.2013 | Доступ: свободный | Студентов: 542 / 46 | Длительность: 11:51:00
Лекция 6:

Абстрактный тип данных

Все должно быть изложено так просто, как только возможно, но не проще.

Альберт Эйнштейн

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

5.1. Понятие абстрактного типа данных

В основу самой абстракции положено выделение некоторых важных свойств исследуемого объекта и игнорирование несущественных.

Любой программный проект можно рассматривать с двух точек зрения:

  1. абстрагируясь от деталей реализации, программа соответствует некоторой решаемой задаче реального мира;
  2. с точки зрения реализации программа представляет собой последовательность операторов, предназначенных для решения задачи.

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

A = B; C = do1(A.b); D = do1(A.a);
if (C + D) == B.a return(A.b);
else return("");  
    

В случае применения более значимых имен код приобретает более понятную форму:

TextCRC = CountFieldCRC(tempMessage.Text);
TimeCRC = CountFieldCRC(tempMessage.Time);
if (NameCRC + TimeCRC) == tempMessage.Time) 
  return(tempMessage.Text) ;
else
  return("");  
    

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

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

  • сам тип данных (способ представления и множество значений);
  • набор операций, определенных для этого типа.

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

Все типы языка программирования описываются теми же двумя компонентами.

Например, для типа BOOL множество допустимых значений имеет вид: {TRUE, FALSE}.

И над ними могут быть определены операции (функции):

AND, OR, NOT, EQ.  
    

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

Программу, которая использует абстрактный тип данных, называют клиентской, пользовательской или импортирующей программой. Такая программа может использовать тип, операции над абстрактным типом и, в свою очередь, описывать новые типы на его основе. При этом ей совсем не обязательно знать внутреннюю структуру типа или особенности его реализации, так как ей доступно множество операций над типом. Таким образом, достигается сокрытие подробностей, разделение использования типа от его реализации и внутреннего представления. Этот подход отделяет то, ЧТО представляет определенный тип, от того, КАК он это делает.

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

Рассмотрим, например, в качестве абстрактного типа простую дробь. Дело в том, что многие процессоры специального назначения не реализуют операции над числами с плавающей точкой. Простой заменой им является определение типа "Дробь" (Fraction) - записи с полями "Числитель" (Numerator) и "Знаменатель" (Denominator). При этом для простоты положим, что числитель будет хранить знак дроби, а знаменатель будет рассматриваться как беззнаковое целое:

typedef struct
{
  int Numerator;
  unsigned int Denominator;
} FRACTION;  
    

Пока можно не уточнять, проводится ли сокращение дроби или дробь 4/8 для нас так же хороша, как 1/2.

Абстрактный тип данных является компонентом, который может быть повторно использован. "Хорошая" абстракция скрывает все незначительные детали, отражая лишь важнейшие свойства объекта. Кроме того, абстракция не требует знаний по ее использованию. Т.е. абстрактный тип не зависит от клиентских программ, которые его используют. В свою очередь клиентская программа не обязательно должна знать внутреннюю реализацию абстрактного типа. Таким об-разом, абстрактный тип можно рассматривать как "черный ящик". Эта его особенность упрощает построение больших и сложных программных систем, позволяя выносить пользовательские типы в библиотеки модулей и не "изобретать колесо" по нескольку раз.