Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по технологии программирования.doc
Скачиваний:
95
Добавлен:
02.05.2014
Размер:
68.61 Кб
Скачать

1.2.4. Проблемы абстракции данных.

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

classpoint{ /* … */} // класс точка

class color{/* … */} // класс цвет

Тогда shapeможно описать следующим образом:

enum kind {circle, triangle, square}

// {окружность, треугольник, квадрат}

class shape

{

point center;

color col;

// …

public:

point where() {return center;}

void move(point to) {center = to; draw();}

virtual void draw();

virtual void rotate(int);

// другие операции

}

«Поле типа» - переменная К – необходима для того, чтобы в операциях, таких, как drow() иrotate(), было известно, с какой разновидностью фигуры они работают (в языках, подобных Паскалю, для этих целей можно применить запись из вариантов с использованиемtagk). Функциюdrowможно описать как:

void shape::draw()

{

switch (k)

{

case circle:

// изобразить окружность

break;

case triangle:

// изобразить треугольник

break;

case square:

// изобразить квадрат

break;

}

}

Это определенно недоразумение. Получается, что функции, подобные drowдолжны быть «информированы» о всех фигурах, которые только существуют. Код такой функции увеличивается в размерах по мере добавления в систему новых фигур. Если вы включаете в систему еще одну фигуру, то нужно проверить и, скорее всего, модифицировать каждую операцию, работающую с фигурами. Невозможно добавить новую фигуру в систему без доступа к исходному коду каждой операции. По сколько включению новых фигур подразумевает воздействие на код каждой существенной операции над фигурами, это потребует большой сноровки и будет служить потенциальным источником появления ошибок в коде, который относится к другим, более ранним, фигура. Выбор способа представления данных для конкретных фигур сильно ограничивается тем требованиям, что по крайне мере часть их данных должна помещена в ограниченный каркас, представленный описанием общего типаshape.

1.2.5. Объектно-ориентированное программирование.

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

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

Class shape

{

point center;

color col;

// …

public:

point where() {return center;}

void move(point to) {center = to; draw();}

virtual void draw();

virtual void rotate(int);

// …

}

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

void rotate_all(shape v[], int size, int angle)

// повернуть все элементы массива «v» имеющего «size» компонент,

// на угол «angle»

{

int i = 0;

while (i<size)

{

v[I].rotate(angle);

i = i + 1;

}

}

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

class circle : public shape

{

int radius;

public:

void draw() {/* … */};

void rotate(int) {}

// именно так, функция с пустым телом

}

В С++ класс circleназывается производным от классаshape, а классshape– базовым дляcircle. В другой терминологииcircleshapeназываются подклассом и суперклассом соответственно.

Парадигма программирования приобретает окончательный вид.

«Определите, какие классы вам нужны; заготовте полный набор операций для каждого класса; выразите общие свойства явным способом, используйте наследование»

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

Нахождение общности между типами в системе – далеко не тривиальный процесс, на который решающим образом влияет способ её проектирования. Когда система проектируется, необходимо осуществлять активный поиск по двум направлениям:

а) разрабатывать как строительные блоки для других типов, и

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

8