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