Письмо 01 - Страница 03

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

Можно рассмотреть следующий пример, иллюстрирующий данное решение:
enum Figures line, rectangle, circle
MaxFigure = circle
;объявляем типы
Struc tLine
     x dd ? ; x - координата якорной точки
     y dd ? ; y - координата якорной точки
     x1 dd ? ; x – координата конца линии
     y1 dd ? ; y – координата конца линии
ends tLine

Struc tRectangle
     x dd ? ; x – координата якорной точки
     y dd ? ; y – координата якорной точки
     width dd ? ; ширина прямоугольника
     height dd ? ; высота прямоугольника
ends tRectandle

Struc tCircle
     x dd ? ; x – координата якорной точки
     y dd ? ; y - координата якорной точки
     radius dd ? ; радиус окружности
ends tCircle

DataSeg
     ; создаём экземпляры типов, объявленных ранее
     aLine tLine <20, 20, 10, 15> ; линия
     aRectangle tRectangle <10, 10, 20, 20> ; прямоугольник
     aCircle tCircle <20, 20, 10>  ; окружность

CodeSeg
Start: ; начинаем программу
...
; рисуем линию
call Draw, line, offset aLine
...
;рисуем прямоугольник
call Draw, rectangle, offset aRectangle
...
; рисуем окружность
call Draw, circle, offset aCircle
...

Procedure Draw
Arg @@figure :dword, \ это параметр типа фигуры
@@ref :dword ; это параметр ссылка на фигуру
DataSeg
 Label @@vector :dword ; создаём переключатель
dd offset @@draw line ; адрес кода рисования линии
dd offset @@draw rectangle ; адрес кода рисования прямоугол.
dd offset @@draw circle ; адрес кода рисования окружности
CodeSeg
  mov eax,[@@figure] ; тип фигуры помещаем в eax
cmp eax,MaxFigure ; проверка выхода за диапазон
ja @@type_mismatch ; в случае выхода переход на
; обработчик ошибки
mov edx,[@@ref] ; адрес структуры помещаем в edx
jmp [eax*4 + offset @@vector] ; переход на рисование
; фигуры заданного типа
@@exit: ret ; возврат из подпрограммы
@@type_mismatch: ; обработка ошибки выхода
... ; за диапазон
@@draw_line: ; рисование линии
...
@@draw_rectangle: ; рисование прямоугольника
...
@@draw_circle: ; рисование окружности
...
endp Draw
... ; продолжаем программу
end Start ; завершаем программу

Простой и эффективный переключатель, представленный в примере, позволяет связать виртуальность данных с кодом. Подпрограмме требуется всего несколько операторов, чтобы проверить правильность типа фигуры и перейти к заданному обработчику. Название «переключатель» заимствовано из языков высокого уровня и соответствует операторам switch - case в C или case в Pascal. Качественный компилятор с языка высокого уровня в подобной ситуации должен привести эти операторы к подобной конструкции, поскольку она является наиболее эффективной и компактной.

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

В данном примере предполагается, что подпрограмма умеет работать с некоторыми геометрическими фигурами, в частности, рисовать их. Но для работы программы одного рисования может оказаться недостаточным. Вполне возможно, что фигуры потребуется выделять при выборе, перемещать, скрывать и т.п. Конечно, не так трудно реализовать эти действия по отношению к заданному множеству геометрических фигур, воспользовавшись подпрограммой Draw как шаблоном. Однако, что произойдёт в случае, если потребуется ввести в программу новую геометрическую фигуру? Прежде всего, потребуется серьёзно переделать и перекомпилировать всю программу. Нам придётся:

1. Добавить новую фигуру в тип перечисление Figures.
2. Изменить значение константы MaxFigures.
3. Описать структуру атрибутов новой фигуры.
4. Создать статически или динамически экземпляр новой фигуры.
5. В каждой подпрограмме, выполняющей некоторое действие над фигурой, необходимо добавить:

a. новый элемент в вектор обработчиков, с указанием адреса обработчика;
b. написать собственно сам обработчик действия для новой фигуры;

6. Перекомпилировать программу.

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

Сайт Alexus Software Development