Содержание

2.10 Введение в атрибуты типов

1. Понятие о атрибутах типов

В каком-то смысле, атрибуты типов, также, как и атрибуты параметров относятся к метапрограммированию. Но в львиной своей доле, эти атрибуты обеспечивают поддержку парадигмы объектно-ориентированного программирования (ООП). В многочисленных примерах, которые уже встречались по ходу этого учебника, объекты использовались везде, где можно. И переменные, и записи, и типы — всё это объекты. Но для полноценной поддержки ООП маловато просто использовать типы данных, с привязкой к ним процедур (которые, в лексике ООП, становятся методами). Нехватает несколько существенных штрихов. Вот роль таких штрихов и выполняют атрибуты типов. [↑]

2. Абстрактные типы

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

MODULE Test_abstract;

TYPE
    tVector = ABSTRACT RECORD
        x: REAL;
        y: REAL;
    END;

VAR
    v: tVector;

BEGIN
END Test_abstract.

Компилятор прозрачно намекает, что так делать нельзя. Исправленный пример приводится ниже:

MODULE Test_abstract;

TYPE
    tVector = ABSTRACT RECORD
        x: REAL;
        y: REAL;
    END;

    tMyVector1 = RECORD (tVector)
        z: REAL;
    END;

    tMyVector2 = RECORD (tVector)
        v: REAL;
    END;

VAR
    v: tMyVector1;
    v1: tMyVector2;

BEGIN
END Test_abstract.

Как видно из примера, вслед за типом tVector определяется тип-потомок tMyVector1, который в качестве базового использует абстрактный тип tVector. И такой тип уже не является абстрактным. В типе tMyVector1 добавляется поле z, которого нет в базовом типе. А в его родственнике по базовому типу, добавляется поле v, того же типа, что и z, но эти два типа уже обладают разным поведением, и это разные типы, хотя действия, определённые для базового типа tVector к ним обоим успешно применимы (в приведённом примере у базового типа методы не описаны).

Теперь, сколько бы типов-потомков ни было, все они повязаны базовым абстрактным типом. Достаточно будет изменить базовый тип, и все типы-потомки тут же изменятся. Это экономия труда программиста, упрощения модификации кода, облегчение поиска ошибок. [↑]

3. Расширяемые типы

Расширяемые типы чем-то похожи на абстрактные. Отличие состоит в том, что тип, описанный как расширяемый, может быть использован сразу: без необходимости наследовать его, прежде чем, создать экземпляр типа. Пример использования представлен ниже:

MODULE Test_extensible;

TYPE
    tVector = EXTENSIBLE RECORD
        x: REAL;
        y: REAL;
    END;

    tMyVector1 = RECORD (tVector)
        z: REAL;
    END;

VAR
    v: tVector;
    v1: tMyVector1;

BEGIN
END Test_extensible.

Абстрактный тип, также может расширяться в типах-потомках, но не может быть реализован. Далеко не всегда в качестве базового типа необохдимо использовать абстрактный тип. Да и гораздо чаще возникает необходимость расширить существующий тип, чем создавать тип с чистого листа. Стоить отметить, что раз введено такое семантическое свойство, как «расширяемый тип», очевидно, что без ключевого слова EXTENSIBLE тип расширить нельзя. Это поведение типа по умолчанию (как и сокрытие данных). [↑]

4. Ограниченные типы

Ограниченные типы не ограничены ни в чём, кроме того, где могут быть созданы экземпляры таких типов. А именно: в том же модуле. У работающих программистов, которые слегка подучивается (в отличии от правильного подхода: учиться, слегка подрабатывая) бывает время от времени возникает соблазн создать переменные нужного типа, которые в документации явно описаны, как служебные или для частного использования. Добраться до них тяжело, так зачем себе усложнять жизнь? Нужно обратиться к ним напрямую, несмотря на открытые предупреждения в документации. Этот подход похож на ранее упомянутый анекдот: «Баня, через дорогу переодевалка». Вот для таких целей и служит атрибут ограничения размещения типов.

Пример приводится ниже:

MODULE Test_limited;

TYPE
    tVector = LIMITED RECORD
        x: REAL;
        y: REAL;
    END;

    tMyVector1 = RECORD (tVector)
        z: REAL;
    END;

VAR
    v: tVector;
    v1: tMyVector1;

BEGIN
END Test_limited.

Как видно в примере, при попытке его скомпилировать, возникла ошибка. Так как тип-потомок также должен наследовать атрибут ограничения.

Исправленный пример ниже:

MODULE Test_limited;

TYPE
    tVector = LIMITED RECORD
        x: REAL;
        y: REAL;
    END;

    tMyVector1 = LIMITED RECORD (tVector)
        z: REAL;
    END;

VAR
    v: tVector;
    v1: tMyVector1;

BEGIN
END Test_limited.

И как видно, никаких вопросов у компилятора к программисту не возникает. [↑]

5. Указательные типы

Указательные типы очень часто встречаются в программном коде. Они представляют из себя ссылку на какой-либо тип. Ссылка, физически, — это адрес в памяти, с которого начинается структура, описываемая таким типом. Метаинформации здесь почти нет, речь идёт о части самого языка.

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

  1. Компилятор не создаёт сам экземпляр указательного типа, экземпляр нужно создавать руками с помощью оператора NEW. Природа указательного типа — динамическая.
  2. Компонентный Паскаль обеспечивает начальную инициализацию полей и массивов для указательных типов. В частности, указательные и процедурные переменные устанавливаются в NIL.
  3. При работе с записевыми типами не надо разыменовывать указатели (получать реальное значение переменной по указанному адресу). Компонентный Паскаль прекрасно с этим справляется сам, на основе информации о типе.

Примеры работы с записевыми типами не сильно отличаются от простых типов:

MODULE Test_pointer;

IMPORT Log;

TYPE
    tVector = ABSTRACT RECORD
        x: REAL;
        y: REAL;
    END;

    tPVector = POINTER TO RECORD (tVector)
        z: REAL;
    END;

VAR
    v: tPVector;

PROCEDURE Start*;
BEGIN
    Log.Real(v.x);Log.Real(v.y);Log.Real(v.z);
    Log.Ln
END Start;

BEGIN
    NEW (v);
END Test_pointer.
(!)Test_pointer.Start

компилируется "Test_pointer"   112   4
старый модуль Test_pointer выгружен
0.0 0.0 0.0

В результате выполнения указанного фрагмента будут созданы базовый абстрактный тип tVector, который нельзя реализовать непосредственно. На его базе создаётся указательный тип tpVector, который вполне себе реализуем. При начале работе модуля через NEW создаётся экземпляр указательного типа tpVector, а в экспортируемой процедуре Start выводится значение всех полей. Как видно из вывода, все поля честно установлены в 0.0.

Также следует отметить, что привязка методов к указательному типу выполняется несколько иначе, чем записевму типу:

MODULE Test_pointer;

IMPORT Log;

TYPE
    tPVector = POINTER TO RECORD
        z: REAL;
    END;

VAR
    v: tPVector;

PROCEDURE (v: tPVector)Log(), NEW;
BEGIN
    Log.Real(v.z); Log.Ln
END Log;

PROCEDURE Start*;
BEGIN
    v.Log
END Start;

BEGIN
    NEW (v);
END Test_pointer.
(!)Test_pointer.Start

компилируется "Test_pointer"   92   4
старый модуль Test_pointer выгружен
0.0

Как видно из примера, при описании процедуры Log, при привязке метода к указательному типу, перед именем процедуры вместо

PROCEDURE (VAR self: tpVector)Log(), NEW;

использована форма

PROCEDURE (self: tpVector)Log(), NEW;

Отсутствие ключевого слова VAR (variable — «переменная»), как раз говорит о том, что речь идёт об указательном типе. Можно было сделать и наоборот: записевые типы привязывать без ключевого слова, а указательные — с ключевым. Но поскольку указательные типы используются чаще (они выгодней), методически верно было сделать так, как сделано сейчас.

Также необходимо указать на ещё один момент. В секции импорта указан модуль Log. В модуле также есть процедура с таким именем. Но компилятор считает такое смешение имён допустимым. А всё потому, что Компонентный Паскаль различает по ключевым словам и порядку обращения имя модуля и метод класса (в самом деле, есть разница между Log.Ln и v.Log). [↑]

6. Выводы

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

[ ← Назад ] [ Вверх ↑ ] [ Далее → ]