(*
см. Свердлов С.З.(2007)=ЯП и методы трансляции

               Реализация очереди на Обероне-07 по Вирту.

  Поместим описание (и реализацию) абстрактного типа данных (АТД) «очередь»
  в отдел на Глаголе (модуль) Queue - «очередь».
*)

                  ОТДЕЛ Queue;
ВИД
  УзелД- = ДОСТУП К нУзел;
                    нУзел- = НАБОР (* дескриптор (описатель) вершины\узла *)
            Next : УзелД;
                             КОН;
  tQueue-                  = НАБОР (* Абстрактный Тип Данных (АТД) «очередь» *)
           First,
            Last : УзелД
                             КОН;
(*
    Абстрактный вид\тип 'УзелД' — это ДОСТУП К элементу очереди\списка (node — вершина\узел),
 а сам элемент очереди имеет вид\тип 'нУзел' — дескриптор (описатель) вершины.
 Оконечные минусы, сопровождающие имена (идентификаторы) 'УзелД', 'нУзел' и 'tQueue', 
 означают, что эти виды\типы экспортируются из отдела Queue, то есть доступны
 для использования в других отделах.
 Принципиально важно, что имена полей 'Next', 'First' и 'Last' не экспортируются.

    Отделы, использующие отдел Queue, не будут (и не должны) знать деталей реализации
 очереди — соблюдаются основополагающие принципы АТД — разделение описания (спецификации) и
 реализации, м также упрятывание (сокрытие) реализации.

    С другой стороны, наш отдел Queue не знает о видах\типах элементов,
 которые фактически будут помещаться в очередь.
*)
(*
    Продолжим реализацию (и спецификацию) очереди в отделе Queue.

 Опишем задачи для инициализации очереди (init),
                              добавления (Put)
                            и извлечения (Get) элемента,
 а также логическую задачу-функцию, позволяющую узнать,
 не пуста ли очередь ( НЕпуста():КЛЮЧ ).
*)

ЗАДАЧА (tQueue) Init-(Q+:tQueue);             (* Инициализация АТД «очередь» *)
УКАЗ
  Q.First := ПУСТО;
  Q.Last  := ПУСТО;
КОН Init;

ЗАДАЧА (tQueue) Put- (Q+:tQueue; node+:УзелД);(* Добавить в очередь <node> *)
УКАЗ
  ЕСЛИ
       Q.First = ПУСТО
  ТО
       Q.First := node;
  ИНАЧЕ
       Q.Last.Next := node;
  КОН;
       Q.Last := node;
                 node.Next := ПУСТО;
КОН Put;

ЗАДАЧА (tQueue) Get- (Q+:tQueue; node+:УзелД);(* Удалить из очереди <node> *)
УКАЗ
  node:=Q.First;
        Q.First := Q.First.Next;
  ЕСЛИ
        Q.First = ПУСТО
  ТО
        Q.Last := ПУСТО
  КОН;
КОН Get;

ЗАДАЧА (tQueue) НЕпуста-(Q:tQueue):КЛЮЧ;      (* Очередь не пуста ? *)
УКАЗ
  ВОЗВРАТ Q.First # ПУСТО;
КОН НЕпуста;

КОН Queue.

(*
    Параметром задач Put и Get является, наряду с очередью Q,
 указатель <node> на добавляемую или извлекаемую вершину\узел.

    Программа, использующая задачу Put, перед добавлением в очередь элемента
 должна создать динамическую переменную, а указатель на эту переменную
 передать в качестве параметра задаче Put.

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


    Вызов задачи Get в случае, когда очередь пуста, недопустим
 и приведёт к аварийному завершению приложения (АВОСТ).
    Перед обращением к Get следует предусмотреть (с помощью НЕпуста)
 проверку наличия в очереди элементов.


   Программный интерфейс (перечень экспортируемых ресурсов) написанного
 нами отдела может быть получен автоматически с помощью специального
 инструмента Оберон-системы — смотрителя, либо
 формируется компилятором в ходе трансляции:

DEFINITION Queue;
ВИД
  УзелД = ДОСТУП К нУзел;
                   нУзел = НАБОР КОН;
  tQueue = НАБОР КОН;

ЗАДАЧА Init(Q+:tQueue);
ЗАДАЧА Put(Q+:tQueue; node+:УзелД);
ЗАДАЧА Get(Q+:tQueue; node+:УзелД);
ЗАДАЧА НЕпуста(Q-:tQueue):КЛЮЧ;
КОН Queue.
*)

(*
  Обратите внимание, что поля записей нУзел и tQueue (tNodeDesc и tQueue) скрыты.

              Использование очереди. Наследование
*)
(*
  Теперь рассмотрим, каким образом можно использовать реализованную
  нами очередь. Будем записывать в очередь целые и вещественные числа.
  Для этого в программе, которая оперирует очередью
 (пусть это будет отдел Test) нужно определить два новых типа записей
  и два соответствующих типа указателей.
  Одна пара типов (запись и указатель на нее) будет представлять элемент очереди,
  содержащий целочисленное поле, другая пара — вещественное.
  Оба типа записей должны быть расширением нУзел, а
  типы указателей — расширением типа УзелД.
  Только в этом случае мы сможем обеспечить при вызове задач Get и Put
  совместимость типов для параметра, обозначающего указатель
  на добавляемую или извлекаемую вершину (параметр node).
  По правилам языка Оберон, указатель типа УзелД может ссылаться
  на переменную-запись, тип которой или нУзел, или
  является расширением типа нУзел.
*)
                    ОТДЕЛ Test;
ИСПОЛЬЗУЕТ
  Queue,
  наЭкран ИЗ "..\..\..\..\..\..\..\..\Отделы\ВводВывод\";
ВИД
  нЦУзелД = ДОСТУП К нЦУзел;
                     нЦУзел = НАБОР (Queue.нУзел) 
         x:ЦЕЛ;                              (* Целочисленное поле *)
                              КОН;
  нВУзелД = ДОСТУП К нВУзел;
                     нВУзел = НАБОР (Queue.нУзел)
         x:ВЕЩ;                              (* Вещественное поле *)
                              КОН;
(*
  Модуль Test импортирует отдел Queue и отдел наЭкран,
  содержащий элементарные средства вывода, которые потребуются нам
  для иллюстрации действий с очередью.
  Запись в скобках после слова record означает, что определяемый тип
  является расширением типа, указанного в скобках
  (в нашем случае Queue. нУзел — типа нУзел из отдела Queue).
  Если нЦУзел И нВУзел — расширения нУзел, то соответствующие
  типы-указатели нЦУзелД и нВУзелД считаются расширениями (наследниками) УзелД.
  Записи нЦУзел и нВУзел наследуют от типа нУзел поле Next,
  хотя и «не знают» об этом.
  Запись нЦУзел расширяет тип нУзел полем х целого типа,
  a нВУзел — полем вещественного типа, также названным х.
*)
(*
  Действия с очередью разместим в задаче Run, которая является
  частью отдела Test. Предусмотрим описание необходимых переменных.
*)

ЗАДАЧА Run-; (* Экспортируется *)
ПЕР
     Q:Queue.tQueue;   (* Очередь    *)
   int:нЦУзелД;        (* Указатель    на цел. вершину *)
  real:нВУзелД;        (* Указатель    на вещ. вершину *)
  node:Queue.УзелД;    (* Указатель    на (абстрактную) вершину, т.е. любого вида *)
     i:ЦЕЛ;            (* Счетчик цикла    *)
УКАЗ

              (* Заполнение очереди *)

(*
  Для иллюстрации будем помещать в очередь попеременно
  целые и вещественные значения. Запишем 10 пар.
  В качестве целого будем брать значение счетчика цикла i,
  в качестве вещественного — i/10.
*)
  Queue.Init(Q); (* Инициализация очереди Q *)

                 (* Заполнение очереди *)
       i:=1;
  ПОКА i<=10 ВЫП
    СОЗДАТЬ(int);
            int.x := i;
    Queue.Put(Q, int);
    СОЗДАТЬ(real);
            real.x := i/10;
    Queue.Put(Q, real);
    УВЕЛИЧИТЬ(i);
  КОН;

(*
     Добавление каждого элемента в очередь выполняется в три приема.
  Вначале с помощью вызова стандартной задачи СОЗДАТЬ() создается динамическая переменная.
  При вызове СОЗДАТЬ(int)  она будет иметь вид\тип нЦУзел,
  при вызове СОЗДАТЬ(real) создаётся переменная вида\типа нВУзел.
  Затем полю х (в одном случае это поле целого вида\типа, в другом — вещественного) присваивается значение.
  Наконец, вызовом задачи Put элемент добавляется в очередь.
  Следует обратить внимание, что при двух вызовах Put вместо параметра node этой задачи подставляются
  указатели разных типов (нЦУзелД и нВУзелД). Но оба эти вида\типа — расширения УзелД,
  поэтому такая подстановка допустима.
*)
(*
               Обслуживание очереди. Проверка и охрана типа
*)
(*
Теперь предусмотрим (в задаче Run) обслуживание очереди, которое будет состоять просто в печати значений содержащихся в ней чисел (с удалением элементов из очереди). При этом мы, конечно, не вправе пользоваться знаниями о том, в каком порядке в очереди следуют целые и вещественные. Однако будем считать, что, кроме целых и вещественных, других типов значений в очереди нет.
*)

  ПОКА
       Queue.НЕпуста(Q)
  ВЫП
       Queue.Get(Q,node);
                           (* Напечатать значение элемента *)
        НаЭкран.ВсяЦепь('^')         (* Переход на новую строку *)
  КОН;

(*
Это лишь набросок цикла обслуживания очереди. Вместо фрагмента, где должна быть печать извлеченного из очереди значения — многоточие. Рассмотрим, как следует заполнить этот пробел. Во-первых, обратите внимание, что при обращении к задаче Get вместо выходного параметра подставлена переменная-указа-тель node типа УзелД. Это единственно возможное решение в этой ситуации. Записать Queue.Get (Q, int) ИЛИ Queue . Get (Q, real) нельзя ПО ТОЙ простой причине, что мы не знаем, какого типа будет очередной элемент. Кроме того, это нарушило бы правило совместимости типов Оберона и привело бы к ошибке при компиляции.
*)
(*
Чтобы напечатать значение числового поля полученного из очереди элемента, придется проверить тип этого элемента, точнее — тип соответствующего указателя. Для этой цели используем операцию проверки типа языка Оберон — is.
*)

  ЕСЛИ node ЯВЛЯЕТСЯ нЦУзелД
  ТО                        (* Печатать целочисленное поле x *)
  ИНАЧЕ                     (* Печатать вещественное  поле х *)
  КОН;

(*
Условие node ЯВЛЯЕТСЯ нЦУзелД истинно, если указатель node ссылается на элемент типа нЦУзел, то есть node имеет динамический тип цУзел.
*)
(*
Как же выполнить печать, когда тип полученного элемента уже выяснен? Во-первых, отдел наЭкран предоставляет две разных задачи для печати целых и вещественных — наЭкран.Цел4числа и наЭкран.Вещ4числа соответственно. Использовать наЭкран.Цел4числа(node.x,16) (здесь 16 — ширина поля вывода) нельзя. Дело в том, что переменная node имеет статический тип УзелД, который не предусматривает поля х у той записи, указателем на которую он является. Поэтому обращение node.x некорректно. Хотя мы знаем (благодаря предусмотренной проверке), что фактически node будет ссылаться в первом случае на запись с целочисленным, а во втором — с вещественным полем х. Воспользоваться этим можно, лишь предусмотрев контроль фактического типа node непосредственно в момент использования node для обращения к полю х. Конструкция Оберона, обеспечивающая такой контроль, называется «охрана типа». Используя охрану, получим:
*)

  ЕСЛИ
       node ЯВЛЯЕТСЯ нЦУзелД
  ТО
      НаЭкран.Цел4числа("^ node(нЦУзелД).x = %d",
                           node(нЦУзелД).x,0,0,0)
  ИНАЧЕ
      НаЭкран.Вещ4числа("^ node(нВУзелД).x = %f",
                           node(нВУзелД).x,0,0,0)
  КОН;

(*
  Запись node(нЦУзелД) означает, что при выполнении программы будет проверен фактический тип node. Если этот вид\тип — нЦУзелД (или расширение нЦУзелД), то переменная node рассматривается как имеющая тип нЦУзелД (т.е. обозначение node(нЦУзелД) представляет переменную вида\типа нЦУзелД) и, следовательно, наличие целочисленного поля х у записи, на которую ссылается node, в этом случае гарантированно. В этом и состоит смысл охраны типа — она обеспечивает безопасное обращение с указателями в строгом соответствии с их фактическим типом. Если динамический тип node не равен нЦУзелД и не является расширением нЦУзелД, при выполнении программы происходит ошибка.
*)
(*
Целиком фрагмент, отвечающий за обслуживание очереди, теперь выглядит так:
*)

  ПОКА
       Queue.НЕпуста(Q)
  ВЫП
       Queue.Get(Q, node);
                        (* Передать объекту, на который ссылается указатель node, сообщение "Напечатать значение" *)
    ЕСЛИ
         node ЯВЛЯЕТСЯ нЦУзелД
    ТО
      НаЭкран.Цел4числа("^ node(нЦУзелД).x = %d",
                           node(нЦУзелД).x,0,0,0)
    ИНАЧЕ
      НаЭкран.Вещ4числа("^ node(нВУзелД).x = %f",
                           node(нВУзелД).x,0,0,0)
    КОН;
        НаЭкран.ВсяЦепь('^')         (* Переход на новую строку *)
  КОН;

(*
  Такое решение, однако, может вызывать критику, поскольку не соответствует принципам объектного подхода.
  Выяснение типа объекта, полученного из очереди, и выбор варианта поведения происходят на верхнем
  уровне управления. Тем самым объекты лишаются активной роли.
  Вместо этого следовало бы просто передать объекту сообщение: «А ну-ка, напечатайся».
  Организацией такого решения мы сейчас и займемся.
*)

(*
              Обработка сообщений на Обероне-07
*)
(*
  Общая схема обслуживания очереди, основанная на передаче сообщений с переносом активности
  на уровень объектов, будет выглядеть так:
*)
  ПОКА
        Queue.НЕпуста(Q)
  ВЫП                     (* Передать объекту, на который ссылается указатель node,
                             сообщение "Напечатать значение" *)
        Queue.Get(Q,node);
        НаЭкран.ВсяЦепь('^')         (* Переход на новую строку *)
  КОН;

(*
Передача сообщения будет выполняться с помощью вызова задачи (которая наверняка будет называться Print — печатать). Эта задача могла бы выглядеть так:
*)

ЗАДАЧА Print(node-:Queue.УзелД);
УКАЗ
  ЕСЛИ
       node ЯВЛЯЕТСЯ нЦУзелД
  ТО
    НаЭкран.Цел4числа("^ node(нЦУзелД).x = %d",
                         node(нЦУзелД).x,0,0,0)
  ИНАЧЕ
    НаЭкран.Вещ4числа("^ node(нВУзелД).x = %d",
                         node(нВУзелД).x,0,0,0)
  КОН
КОН Print;

(*
Но в этом случае объекты снова лишаются самостоятельности. Просто появляется промежуточный уровень в виде задачи Print, на который и переносится решение всех вопросов, связанных с печатью значений. Плохо и то, что при появлении новых типов объектов, которые мы захотим помещать в очередь и значения которых будем печатать, придется переписывать (и перекомпилировать) задачу Print. То есть добавление нового программного кода (новых типов объектов) будет вынуждать нас менять уже существующий код. Лучше бы без этого обходиться, поскольку такая ситуация способствует снижению надежности программы из-за необходимости постоянной модернизации уже написанного и отлаженного кода. Возможность расширять программу, не вторгаясь в уже существующие ее части, крайне важна44. И рассматриваемая нами технология это позволяет.
*)
(*
Предусмотрим у каждого объекта, который мы добавляем в очередь, поле задачного вида\типа, значением которого будет задача (своя для каждого типа объекта), способная напечатать значение объекта данного типа.
*)
(*
Для объекта, содержащего целочисленное значение (объекта вида\типа нЦУзелД), это будет следующая задача:
*)

ЗАДАЧА Printlnt(node-:Queue.УзелД);
УКАЗ
    НаЭкран.Цел4числа("^ node(нЦУзелД).x = %d",
                         node(нЦУзелД).x,0,0,0)
КОН Printlnt;

(*
Может показаться, что параметр node этой задачи можно было сделать типа нЦУзелД, и тогда не потребовалось бы использовать охрану типа при обращении к полю х. Ведь охрана типа приводит к дополнительным затратам во время выполнения программы. Но делать так не следует, поскольку все задачи, исполняющие роль обработчиков сообщений «напечатать», должны иметь один и тот же тип параметра, то есть быть одинакового задачного вида\типа.
*)
(*
Для объектов типа нВУзелД роль обработчика сообщений будет выполнять задача PrintReal.
*)

ЗАДАЧА PrintReal(node-:Queue.УзелД);
УКАЗ
    НаЭкран.Вещ4числа("^ node(нВУзелД).x = %f",
                         node(нВУзелД).x,0,0,0)
КОН PrintReal;

(*
Если считать, что каждый объект (объект каждого типа), который мы записываем в очередь и извлекаем оттуда, должен уметь напечатать свое значение, то поле задачного типа (это будет поле Print), означающее задачу, которая печатает значение объекта, следовало бы поместить в описание типа нУзел в отделе Queue. Но делать так мы не будем потому, что это противоречило бы первоначальному замыслу: создать и никогда больше не изменять отдел для работы с очередью. Ведь кроме необходимости печатать, может возникнуть и сколько угодно других потребностей при работе с объектами. И каждый раз менять отдел Queue? Ни в коем случае!
*)
(*
С другой стороны, неразумно помещать поле Print и в описание каждого конкретного вида\типа объектов (нЦУзел, нВУзел). Это поле общее для таких объектов, общее и по названию и по виду\типу, поэтому было бы правильно, если бы объекты этих типов унаследовали поле Print от общего типа-предка. Кроме того, если поля Print, пусть и одноименные (как поля х), определить для каждого типа в отдельности, то для обращения к этим полям снова пришлось бы различать типы объектов прямо в цикле обслуживания очереди, от чего мы стремимся уйти.
*)
(*
Назовем новый, общий для нЦУзелД и нВУзелД, тип-предок tPrintable — тот, который «умеет» напечатать свое значение. Его описание поместим в разделе типов отдела Test, который теперь будет выглядеть так:
*)

ВИД
  tPrintable = ДОСТУП К tPrintDesc;
                        tPrintDesc = НАБОР (Queue.нУзел);
     Print:ЗАДАЧА (node: Queue.УзелД);
                                     КОН;
  нВУзелД    = ДОСТУП К нВУзел;
                        нВУзел     = НАБОР (tPrintDesc)
     x : ВЕЩ;
                                     КОН;
  нЦУзелД    = ДОСТУП К нЦУзел;
                        нЦУзел     = НАБОР (tPrintDesc)
     x : ЦЕЛ;
                                     КОН;
(*
Типы нЦУзелД и нВУзелД стали (непосредственными) расширениями tPrintable, а уже этот тип — непосредственное расширение типа УзелД из отдела Queue, нЦУзелД и нВУзелД остаются при этом (не непосредственными) расширениями УзелД (рис. 1.18).

  УзелД     УзелД
--------  -------
нЦУзелД   нВУзелД

Исходная    Новая
иерархия    иерархия

Рис. 1.18. Иерархия типов объектов
*)

(*
Замечу, что программа никогда не будет создавать объекты типа tPrintable. В записи соответствующего типа нет даже поля, содержащего значение объекта (вроде поля х). Отсутствует и задача (вроде Printint или PrintReal), которая способна печатать значение объектов такого типа. Подобные типы, которые служат лишь промежуточными звеньями в иерархии наследования, являясь носителями некоторых общих для своих наследников свойств (спецификации свойств), часто называют абстрактными (не путать с концепцией АТД).
*)
(*
Запишем теперь по-новому, в объектном стиле, обслуживание очереди. Но перед этим нужно модернизировать и ее заполнение, поскольку у всех объектов, которые мы записываем в очередь, появилось поле Print, которое необходимо задать.
*)

                        (* Заполнение очереди *)
       i:=1;
  ПОКА i<=10 ВЫП
    v
    СОЗДАТЬ(int);
            int.x := i;
            int.Print := Printlnt;

    Queue.Put(Q, int);

    СОЗДАТЬ(real);
            real.x := i/10;
            real.Print := PrintReal;

    Queue.Put(Q, real);
    УВЕЛИЧИТЬ(i);
  КОН;

(*
При заполнении поля Print (выполнении присваивания, подобного int. Print : = Printlnt) есть риск ошибиться, назначив объекту обработчик сообщения, не соответствующий типу этого объекта. Ведь все обработчики однотипны (в смысле задачного типа), и компилятор не обнаружит несоответствия, если мы, к примеру, запишем i nt. Print : = PrintReal. Риск можно снизить, а заодно сделать программу более структурированной, если для создания объектов, назначения им обработчиков сообщения и заполнения поля значения использовать специальные задачи или даже задачи-функции.
*)

ЗАДАЧА Создать_нЦУзелД(i:ЦЕЛ):нЦУзелД;
ПЕР
  int : нЦУзелД;
УКАЗ
  СОЗДАТЬ(int);
  int.x := i;
  int.Print := Printlnt;
  ВОЗВРАТ int
КОН Создать_нЦУзелД;

ЗАДАЧА Создать_нВУзелД(r:ВЕЩ):нВУзелД;
ПЕР
  real : нВУзелД;
УКАЗ
  СОЗДАТЬ(real);
          real.x := r;
          real.Print := PrintReal;
  ВОЗВРАТ real
КОН Создать_нВУзелД;

(*
  Используя задачи-функции (конструкторы) Создать_нЦУзелД и Создать_нВУзелД,
  которые создают объекты в памяти, инициализируют их поля и возвращают
  в качестве своего значения указатели на созданные объекты,
  то заполнение очереди можно записать компактно и выразительно:
*)
           (* Заполнение очереди *)
       i:=1;
  ПОКА i<=10 ВЫП
    Queue.Put(Q, Создать_нЦУзелД(i));
    Queue.Put(Q, Создать_нВУзелД(i/10));
    INC(i);
  КОН;

(*
    В языках Объектный Паскаль, Си++, Ява и Си# функции, подобные Создать_нЦУзелД (Newint) и Создать_нВУзелД (NewReal), называются конструкторами. Соответственно, в этих языках есть и немало правил, регулирующих использование конструкторов и заодно усложняющих изучение и реализацию языка. В Обероне, где нет понятия «конструктор» (как нет и понятия «объект»), мы, тем не менее, легко такие конструкторы создаем, обходясь обыкновенными задачами.
*)
(*
            И вот — обслуживание очереди в объектном стиле:
*)
  ПОКА
       Queue.НЕпуста(Q)
  ВЫП
       Queue.Get(Q,node);
                   node(tPrintable).Print(node);
        НаЭкран.ВсяЦепь('^')         (* Переход на новую строку *)
  КОН;

(*
Здесь при обращении к полю Print объекта node использована охрана типа, позволяющая убедиться, что объект имеет динамический тип tPrintable (или тип-расширение tPrintable), а следовательно, гарантированно содержит поле Print.
*)


(*
                   Гетерогенная очередь на Обероне-2
*)
(*
Надо сразу сказать, что все программы, написанные в предыдущем разделе на Обероне, являются и правильными программами на Обероне-2. Но язык Оберон-2 содержит связанные задачи, которые позволяют несколько по-другому реализовать объектный подход при работе с очередью.
*)
(*
Использование полей задачного типа для придания объектам индивидуального поведения обеспечивает максимальную гибкость. Значениями таких полей являются задачи-обработчики сообщений. Разные экземпляры объектов одного и того же типа могут иметь разные значения задачных полей, а значит, обладать различным поведением. Но такая гибкость часто избыточна. Однотипные объекты и вести себя должны, скорее всего, одинаково. Кроме того, наличие полей задачного вида\типа у каждого экземпляра объекта увеличивает расход памяти и требует инициализации таких полей каждый раз, когда объект создается, что связано с дополнительными затратами ресурсов, а неверная инициализация или ее отсутствие — потенциальная причина ошибок.
*)
(*
Процедуры-обработчики сообщений могут быть связаны не с экземплярами, а с типами объектов. Подобные задачи в Обероне-2 и называются «связанными с типом процедурами» (type-bound procedures), или просто «связанными процедурами». В некоторых языках подобные процедуры и функции носят название методов. А типы, с которыми связаны методы, называют классами.
*)
(*
Определим связанные задачи для видов\типов нЦУзелД и нВУзелД. Названия задач будут одинаковы — Print. Предполагается, что задачное поле Print у типа tPrintDesc теперь отсутствует — оно больше не нужно. Роль обработчиков сообщений будут играть связанные с видами\типами задачи.
*)

ЗАДАЧА (нЦУзелД) Print (this-:нЦУзелД);
УКАЗ
    НаЭкран.Цел4числа("^ this.x = %d",
                         this.x,0,0,0)
КОН Print;


ЗАДАЧА (нВУзелД) Print (this-:нВУзелД);
УКАЗ
    НаЭкран.Цел4числа("^ this.x = %f",
                         this.x,0,0,0)
КОН Print;

(*
Отличительным признаком связанных задач является параметр-приёмник, описание которого помещается в скобках перед названием задачи. По типу этого параметра и устанавливается связь задачи с видом НАБОР (типом-записью) или видом ДОСТУП (типом-указателем на запись). В нашем примере параметром-приемником является this — указатель на объект — элемент списка. Внутри связанной задачи параметр-приемник обозначает тот экземпляр объекта (или указатель на экземпляр объекта), который принимает и обрабатывает сообщение. Отсюда и название, которое я выбрал для параметра-приемника (this — этот). В таких языках, как Ява и Си# слово this даже является зарезервированным, а соответствующий параметр передается методу неявно. Явная спецификация параметра-приемника в Обероне-2 существенно повышает наглядность программы и облегчает понимание.
*)
(*
Связанные задачи в некотором отношении похожи на поля записей. Обращение к ним выполняется так же, как и к полям. Существование двух связанных с разными видами\типами задач, имеющих одинаковое название, разрешено, как разрешено определять одноименные поля внутри разных типов (поля х в нашем примере). Но, в отличие от полей, для которых отводится память в каждом экземпляре объекта (переменной-записи), использование связанных задач не предполагает резервирования памяти для ссылок на них в каждом объекте.
*)
(*
Тип, являющийся расширением другого типа, может иметь связанную задачу с тем же названием, что имеется у типа-предка. В этом случае типы параметров таких задач также должны совпадать, а задача типа-наследника переопределяет соответствующую задачу типа-предка.
*)
(*
Как и в предыдущих примерах, чтобы запрограммировать обслуживание очереди в объектном стиле, нам потребуется абстрактный промежуточный тип tPr intable. Но поскольку для обработки сообщений теперь используются не поля задачного типа, а связанные задачи, с этим типом будет связана абстрактная, не выполняющая никакой реальной работы и никогда не вызываемая задача Print.
*)

ВИД
  tPrintable = ДОСТУП К tPrintDesc;
                        tPrintDesc = НАБОР (Queue.нУзел)
                                     КОН;
ЗАДАЧА (this:tPrintable) Print;
КОН Print;

(*
Теперь запишем фрагмент, выполняющий заполнение очереди. В отличие от примеров на Обероне, не требуется заполнять поля задачного типа, которых уже и нет. Кроме того, уместно применить оператор ОТ..ДО, которого нет в Обероне, но есть в Обероне-2.
*)

                              (* Заполнение очереди *)
  ОТ i:=1 ДО 10 ВЫП
    СОЗДАТЬ(int);
            int.x := i;
    Queue.Put(Q, int);
    СОЗДАТЬ(real);
            real.x := i/10;
    Queue.Put(Q, real);
  КОН;

(*
Можно, как и раньше, использовать задачи-конструкторы, которые теперь освобождаются от обязанности заполнять задачные поля.
*)
(*
И, наконец, можем запрограммировать обслуживание очереди.
*)

  ПОКА
       Queue.НЕпуста(Q)
  ВЫП
       Queue.Get(Q,node);                   (* Получить    *)
                   node(tPrintable).Print;  (* Напечатать  *)
        НаЭкран.ВсяЦепь('^')         (* Переход на новую строку *)
  КОН;

(*
Ключевую роль играет здесь оператор node(tPrintable).Print. Исполняется он следующим образом. Вначале с помощью охраны типа проверяется, имеет ли указатель node динамический тип tPrintable или тип, являющийся расширением tPrintable. В нашем случае это условие выполнено, поскольку node указывает либо на элемент типа нЦУзелДDesc, либо на элемент типа нВУзел, то есть node имеет либо динамический тип нЦУзелД, либо — нВУзелД. И тот, и другой являются расширениями tPrintable. Охрана не препятствует дальнейшей работе, поскольку все типы начиная с tPrintable имеют связанную задачу Print. Далее происходит вызов задачи Print, связанной с динамическим типом node. То есть если node имеет динамический тип нЦУзелД, то вызывается Print, связанная с нЦУзелД и умеющая печатать целочисленное значение, если node - вида\типа нВУзелД, то вызывается связанная с этим типом задача Print.
*)
(*
Такой механизм вызова задач, когда фактически вызванная задача становится известна только на этапе выполнения программы, называется поздним связыванием. Впрочем, позднее связывание имеет место и при использовании полей задачного типа. Рассмотрение приведенного фрагмента даег повод упомянуть еще один термин, используемый в разговорах про ООП, — полиморфизм. Проявляется полиморфизм в том, что за некоторыми обозначениями в нашей программе могут скрываться объекты разной природы. Так, в операторе node (tPrintable) .Print идентификатор node может обозначать указатель и на объект с целочисленным значением и на объект с вещественным значением, а если расширить систему типов, то node будет соответствовать еще большему набору (без всякого изменения самого оператора). За названием Print скрываются в действительности три разные задачи.
*)


(*
                          Очередь — тоже объект
*)
(*
Использованный нами объектный подход и связанные задачи в частности, можно применить и к модулю, реализующему операции с очередью. Действительно, если элементы очереди мы рассматриваем как объекты, то почему бы и самой очереди не быть объектом? Препятствий для этого нет. Будем считать очередь активным объектом, и вместо того чтобы вызывать, например, задачу для добавления элемента к очереди и передавать очередь, в которую надо что-то добавить, в качестве параметра этой задаче, мы будем посылать очереди сообщение «добавь элемент». Она и добавит.
*)
(*
На самом деле большой разницы в этих двух подходах нет. Ведь передача сообщения — это не что иное, как вызов задачи. А очередь в качестве параметра этой задаче всё равно передаётся. Только теперь это будет параметр-приемник. И говорим мы обо всем немного другими словами.
*)

(*
  В листинге 1.12 приведен полный текст модернизированного отдела для работы с очередью.

     Листинг 1.12. Модуль для работы с очередью, использующий связанные задачи (Оберон-2)
*)

                         ОТДЕЛ Queue;
ВИД
  УзелД- = ДОСТУП К нУзел;
                    нУзел- = НАБОР
          Next :УзелД
                                КОН;
                    tQueue-   = НАБОР
         First,
          Last :УзелД
                                КОН;

ЗАДАЧА (Q+:tQueue) Init; (* Инициализировать *)
УКАЗ
  Q.First := ПУСТО;
  Q.Last  := ПУСТО;
КОН Init;


ЗАДАЧА (Q+:tQueue) Put-(node+:УзелД);  (* Добавить элемент *)
УКАЗ
  ЕСЛИ
       Q.First - ПУСТО
  ТО
       Q.First := node;
  ИНАЧЕ
       Q.Last.Next := node;
  КОН;
       Q.Last := node;
                 node.Next := ПУСТО;
КОН Put;


ЗАДАЧА (Q+:tQueue) Get-(node+:УзелД); (* Получить элемент *)
УКАЗ
  node := Q.First;
          Q.First := Q.First.Next;
  ЕСЛИ
          Q.First = ПУСТО
  ТО
          Q.Last := ПУСТО
  КОН
КОН Get;


ЗАДАЧА (Q+:tQueue) НЕпустаM): КЛЮЧ; (* He пуста ли очередь ? *)
УКАЗ
  ВОЗВРАТ Q.First # ПУСТО
                         (*  продолжение
                                          Листинг 1.12 (продолжение) *)
КОН НЕпуста;

КОН Queue.
(*
                    Листинг 1.12 (продолжение)
*)
(*
Обратите внимание, что в роли приемников связанных задач используются параметры-переменные типа запись (tQueue). Тип указателя на очередь мы не определяем. Если параметр-приемник — запись, а не указатель на запись, в его описании по правилам Оберона-2 должно присутствовать слово var. Поэтому var используется даже в задаче-функции НЕпуста, которая не изменяет очередь.
*)
(*
Интерфейс отдела Queue, сформированный компилятором JOB, выглядит так: DEFINITION Queue;
*)

ВИД
  УзелД = ДОСТУП К нУзел;
                   нУзел = НАБОР
                              КОН;
  tQueue= НАБОР КОН;

ЗАДАЧА(Q+:tQueue) Init;
ЗАДАЧА(Q+:tQueue) Put(node-:УзелД );
ЗАДАЧА(Q+:tQueue) Get(node+:УзелД );
ЗАДАЧА(Q+:tQueue) НЕпуста():КЛЮЧ;

КОН Queue.

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

    Чтобы восстановить это представление, привожу полный текст отдела Test (листинг 1.13),
 в котором заполняется и обслуживается (в задаче Run) очередь.
 При этом используется последняя версия отдела Queue.
*)
(*
    Листинг 1.13. Использование объектно-ориентированной очереди
*)

                          ОТДЕЛ Test;
ИСПОЛЬЗУЕТ
  Queue,
  наЭкран ИЗ "..\..\..\..\..\..\..\..\Отделы\ВводВывод\";
ВИД
  tPrintable = ДОСТУП К tPrintDesc;
                        tPrintDesc = НАБОР (Queue.нУзел) КОН;

  нЦУзелД = ДОСТУП К нЦУзел;
                     нЦУзел        = НАБОР (tPrintDesc)
         x:ЦЕЛ
                                     КОН;
  нВУзелД = ДОСТУП К нВУзел;
                     нВУзел        = НАБОР (tPrintDesc) 
         x:ВЕЩ
                                     КОН;

                                 (* Связанные с типом НАБОР (классом) задачи (методы класса) *)
ЗАДАЧА (tPrintable) Print (this-:tPrintable);
КОН Print;

ЗАДАЧА (нЦУзелД)    Print (this-:нЦУзелД) ;
УКАЗ
    НаЭкран.Цел4числа("^ this.x = %d",
                         this.x,0,0,0)
КОН Print;

ЗАДАЧА (нВУзелД)    Print (this-:нВУзелД);
УКАЗ
    НаЭкран.Вещ4числа("^ this.x = %f",
                         this.x,0,0,0)
КОН Print;


ЗАДАЧА Создать_нЦУзелД(i:ЦЕЛ):нЦУзелД; (* Конструкторы *)
ПЕР
  int:нЦУзелД;
УКАЗ
  СОЗДАТЬ(int);
          int.x := i;
  ВОЗВРАТ int
КОН Создать_нЦУзелД;

ЗАДАЧА Создать_нВУзелД(r:ВЕЩ):нВУзелД;
ПЕР
  real:нВУзелД;
УКАЗ
  СОЗДАТЬ(real);
          real.x := r;
  ВОЗВРАТ real
КОН Создать_нВУзелД;


ЗАДАЧА Run;
ПЕР
     Q:Queue.tQueue;
  node:Queue.УзелД;
    i :ЦЕЛ;
УКАЗ
                       (* Инициализация *)
    Q.Init();
                       (* Заполнение *)
  ОТ i:=1 ДО 10 ВЫП
    Q.Put( Создать_нЦУзелД(i) );
    Q.Put( Создать_нВУзелД(i/10) )
  КОН;
                       (* Обслуживание *)
  ПОКА Q.НЕпуста() ВЫП
    Q.Get(node);
          node(tPrintable).Print;
    НаЭкран.ЗнакЦепи("^")          (*  Out.Ln *)
  КОН;
КОН Run;

КОН Test.