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