
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называются подклассом и суперклассом соответственно.
Парадигма программирования приобретает окончательный вид.
«Определите, какие классы вам нужны; заготовте полный набор операций для каждого класса; выразите общие свойства явным способом, используйте наследование»
Лакмусовой бумажкой применяемости объектно-ориентированного программирования в отдельно взятой области приложения та степень общности между классами, которую можно использовать, применяя механизмы наследования и виртуальные функции. В некоторых приложениях, таких как интерактивная графика, простор для объектно-ориентированного программирования воистину неисчерпаем. В других областях, таких как классическая арифметика и вычисления в ней, по-видимому, трудно найти возможности для чего-то большего, чем простая абстракция данных, поэтому средства для поддержки объектно-ориентированного программирования там избыточны.
Нахождение общности между типами в системе – далеко не тривиальный процесс, на который решающим образом влияет способ её проектирования. Когда система проектируется, необходимо осуществлять активный поиск по двум направлениям:
а) разрабатывать как строительные блоки для других типов, и
б) тщательно изучать возможности выделения в классах тех свойств, которые могли бы быть переданы базовому классу.