Объектно-ориентированное программирование - это подход к разработке программного обеспечения, основанный на объектах, а не на процедурах. Этот подход позволяет максимизировать принципы мо- дульности и "сокрытия информации". Объектно-ориентированное прог- раммирование базируется на связывании или инкапсуляции структур данных и процедуры, которая работает с данными в структуре, с мо- дулем. Объектно-ориентированный принцип разработки дает много преи- муществ. Например, каждый объект инкапсулирует его структуру дан- ных с процедурой, используемой для работы с экземплярами структу- ры данных. Это позволяет устранить в коде программы внутренние зависимости, которые могут быстро привести к тому, что этот код будет трудно обслуживать. Объекты могут также наследовать из по- рождающего объекта структуры данных и другие характеристики, что позволяет сэкономить усилия и обеспечить прозрачное использование для многих целей больших фрагментов кода. Если вы не имеете большого опыта работы с Турбо Ассемблером, то можете пропустить данную главу, но вернитесь к ней позднее, после того, как прочтете другие главы руководства. Мы включили здесь эту главу, чтобы вы имели представление о данных средствах, на объектно-ориентированное представление в Турбо Ассемблере на самом деле представляет собой достаточно продвинутую тему. Она будет иметь больший смысл, когда вы изучите остальные главы руко- водства.Терминология
Для различных единиц объектно-ориентированного программиро- вания в C++ и Паскале используются различные термины. В этом смысле Турбо Ассемблер более близок к Паскалю, хотя не все терми- ны здесь совпадают. Различия между языками описываются в следую- щей таблице: Терминология объектно-ориентированного программирования Таблица 4.1 ------------------------T----------------------T----------------¬ ¦Турбо Ассемблер ¦ Borland C++ ¦ Турбо Паскаль ¦ +-----------------------+----------------------+----------------+ ¦метод ¦ функция-элемент ¦ метод ¦ ¦ ¦ ¦ ¦ ¦процедура метода ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦объект ¦ класс ¦ объект ¦ ¦ ¦ ¦ ¦ ¦базовый объект ¦ базовый класс ¦ базовый объект ¦ ¦ ¦ ¦ ¦ ¦порождающий объект ¦ порождающий класс ¦ порождающий ¦ ¦ ¦ ¦ объект ¦ ¦ ¦ ¦ ¦ ¦порожденный объект ¦ порожденный класс ¦ порожденный ¦ ¦ ¦ ¦ объект ¦ ¦ ¦ ¦ ¦ ¦поле порожденного ¦ элемент данных ¦ поле ¦ ¦ объекта ¦ ¦ ¦ L-----------------------+----------------------+----------------- Примечание: Эти термины подробнее поясняются в данной главе ниже.Для чего в Турбо Ассемблере используются объекты?
Большинство рассматривают язык Турбо Ассемблера, как язык низкого уровня. Однако Турбо Ассемблер обеспечивает многие средс- тва языка высокого уровня (такие как абстрактные типы данных и простой интерфейс с другими языками). Дополнение Ассемблера объ- ектно-ориентированными структурами данных дает ему возможность создавать объектно-ориентированные программы так же легко, как это делается на языках высокого уровня, с сохранением скорости и гибкости языка Ассемблера.Что такое объект?
Объект состоит из структуры данных и связанных с ней проце- дур (которые называются методами), которые работают с данными, записанными в экземплярах структуры данных. Объект может наследовать характеристики порождающего объек- та. Это означает, что структура данных нового объекта включает структуру данных порождающего объекта, а также новые данные. Кро- ме того, новый объект может вызывать все процедуры порождающего объекта, а также те процедуры методов, которые в нем описываются. Примечание: Для объектно-ориентированного программиро- вания мы настоятельно рекомендуем вам использовать режим Ideal Турбо Ассемблера, поскольку область действия иденти- фикаторов в MASM является глобальной, и вы не сможете раз- личить различные расположения показанных методов. Объект, не имеющий наследования, называется базовым объек- том. Объект, наследующий характеристики других объектов, называ- ется порожденным или производным объектом. В Турбо Ассемблере определено несколько идентификаторов, ко- торые вы можете использовать при описании объектов. Они перечис- лены в следующей таблице: Идентификаторы, определенные для объектов Таблица 4.2 -----------------------------T----------------------------------¬ ¦ Идентификатор ¦ Значение ¦ +----------------------------+----------------------------------¦ ¦ @Object ¦ Текстовая макрокоманда, содержа- ¦ ¦ ¦ щая имя текущего объекта (пос- ¦ ¦ ¦ леднего описанного объекта). ¦ ¦ ¦ ¦ ¦ <имя_объекта> ¦ Тип данных STRUC, описывающий ¦ ¦ ¦ структуру данных объекта. ¦ ¦ ¦ ¦ ¦ @Table_<имя_объекта> ¦ Тип данных TABLE, содержащий ¦ ¦ ¦ таблицу методов объекта. Это не ¦ ¦ ¦ то же самое, что экземпляр таб- ¦ ¦ ¦ лицы виртуальных методов. ¦ ¦ ¦ ¦ ¦ @TableAddr_<имя_объекта> ¦ Метка, описывающая адрес экземп- ¦ ¦ ¦ ляра таблицы виртуальных мето- ¦ ¦ ¦ дов объекта (если она есть). ¦ L----------------------------+-----------------------------------Пример объекта
В качестве примера использования объектов рассмотрим прог- рамму, работающую со связанными списками. Мы будем рассматривать связанных список как объект, содержащий связанный список данных и операций (методов), которые вы можете с ними выполнять. Связанный список данных состоит из указателей на начало ("голову") и конец ("хвост") связанного списка (в нашем примере из-за его гибкости используется двунаправленный связанный спи- сок). Каждый элемент связанного списка представляет собой реали- зацию отдельного объекта. Возможности, необходимые для использования связанного спис- ка, предоставляют следующие операции: - создание связанного списка (выделение для него памяти); - уничтожение связанного списка (освобождение используемой памяти); - инициализация связанного списка; - деинициализация связанного списка; - вставка элемента в середину списка перед существующим эле- ментом; - присоединение элемента к концу связанного списка; - удаление элемента из связанного списка; - возвращение первого элемента связанного списка; - возвращение последнего элемента связанного списка. Имейте в виду, что создание и инициализация, а также уничто- жение и деинициализация методов - это не синонимы. При создании и уничтожении методы create и destroy выделяют и освобождают память для объекта (связанного списка), а методы инициализации и деини- циализации initialize и deinitialize только инициализируют и деи- нициализируют ранее выделенные экземпляры объекта. Вы можете видеть, как объект связанного списка наследуется объектами стека или очереди, поскольку очередь и стек можно реа- лизовать как связанный список с ограниченным числом операций. Например, можно реализовать очередь в виде связанного списка, в котором элементы могут добавляться к концу и извлекаться из нача- ла. Если вы таким образом реализуете очередь, то нужно запретить наследуемые методы связанного списка, которые для очереди недо- пустимы (например, вставку в середину списка).Описание объектов
Описание объекта состоит из описаний структур данных объекта и описаний процедур метода, которые вы можете вызывать для данно- го объекта. Описание объекта не означает создание экземпляра объ- екта. Как это сделать, вы узнаете позднее. Описание базового объекта Когда вы описываете объект, Турбо Ассемблер создает структу- ру STRUC и описывает данные для этого объекта, и таблицу TABLE, которая описывает методы объекта. Описания данных объекта предс- тавляет собой структуру с тем же именем, что и объект. Описания методов объектов записывается в типе данных TABLE с именем @Table _<имя_объекта>. Примечание: Более подробно о применении к описанию объ- ектов директивы STRUC рассказывается в Главе 8. Например, для объекта списка два типа данных описываются следующим образом: list STRUC описывает следующие элементы: list_head указатель dword на начало списка list_tail указатель dword на конец списка @Table_list TABLE описывает следующие методы: construct указатель dword на процедуру list_construct destroy указатель dword на процедуру list_destroy и т.д. Директива STRUC описывает данные для объекта, которые созда- ются каждый раз, когда вы создаете экземпляр объекта. Директива TABLE описывает таблицу используемых по умолчанию для данного описания процедур метода. Турбо Ассемблер поддерживает этот тип данных, он не создает экземпляр таблицы где-либо в памяти прог- раммы. Однако позднее вы увидите, что экземпляр таблицы нужно включить для любого объекта, использующего виртуальные методы. Приведем пример определений объекта для связанного списка: list STRUC GLOBAL METHOD { destroy:dword = list_construct ; процедура-конструктор ; списка init:dword = list_init ; процедура-инициализа- ; тор списка deinit:dword = list_deinit ; процедура-деинициали- ; затор списка virtual insert:word = list_insert ; процедура вставки ; узла списка virtual append:word = list_append ; процедура добавле- ; ния узла списка virtual remove:word = list_delete ; процедура удале- ; ния узла списка virtual first:word = list_first ; процедура первого ; узла списка virtual last:word = list_last ; процедура последне- ; го узла списка } list_head dd ? ; указатель начала ; списка list_tail dd ? ; указатель конца ; списка ENDS Примечание: Ключевое слово METHOD показывает, что вы используете расширенную форму директивы STRUC и определяете объект с именем list (список). Примечание: Каждая запись состоит из имени метода, двоеточия, размера указателя на процедуру метода (WORD для ближних процедур, DWORD для дальних процедур). Далее следу- ет символ равенства, имя процедуры и вызов этого метода. Давайте возьмем данный пример и посмотрим, что происходит. Ключевое слово METHOD указывает на вызов метода. За ним сле- дует описаний процедур метода для данного объекта. Поскольку спи- сок методов занимает более одной строки, описания заключаются в фигурные скобки ({ }). Каждое описание метода сообщает Турбо Ассемблеру, какую про- цедуру ему следует использовать для работы с объектом при вызове имени данного метода. Например, первое описание процедуры метода: construct:dword = list_construct объявляет метод с именем consrtruct, которая является процедурой дальнего типа (указатель в нее записывает DWORD). Фактическим именем процедуры метода является list_construct, что следует оп- ределить где-либо еще в исходном коде. Турбо Ассемблер рассматри- вает метод как виртуальный, если ему предшествует ключевое слово VIRTUAL. Когда вы вызываете такой метод, Турбо Ассемблер будет искать адрес процедуры метода, извлекая его из таблицы, которая присутствует в памяти во время загрузки. В противном случае метод является статическим методом. Это означает, что Турбо Ассемблер может определить его адрес на этапе компиляции. Например, метод construct является статическим методом, а метод insert описывает- ся как виртуальный метод. Позднее в этой главе мы поясним, для чего может понадобиться выбирать виртуальные или статические ме- тоды. Далее за разделом описания процедуры метода непосредственно следует структура данных. Это определение использует синтаксис стандартной директивы STRUC. Данный пример содержит описания на- чала и конца списка. В части описания метода в описании объекта, пока вы не ис- пользуете виртуальные методы, никаких данных в структуру данных объекта не заносится. Эти описания приводят к тому, что Турбо Ас- семблер будет строить отдельную таблицу структуры данных, которая содержит в качестве используемых по умолчанию значений адреса за- данных процедур метода. Для каждого объекта вы должны иметь эк- земпляр данной таблицы, и должны явно поместить таблицу. Как это сделать, мы поясним в данной главе позднее. Так как описание должно присутствовать в модуле, содержащем процедуры метода для объекта (а также включаться в любой исполь- зующий объекты исходный код), вы должны описать сам объект в от- дельном файле, который может включаться (INCLUDE) в ваш исходный код. Мы рекомендуем вам использовать имя файла в виде имя_объек- та.ASO (что означает ассемблируемый объект). В этот файл следует включать только описание объекта. Методы объекта следует разме- щать в другом исходном файле, благодаря чему вы можете включать описание объекта там, где это потребуется. Например, описание объекта связанного списка в предыдущем примере можно было бы раз- местить в файле LIST.ASO. Для определения процедур метода объекта можно использовать файл LIST.ASM. В любую программу, использующую объекты, можно было бы включить LIST.ASO, но не LIST.ASM. Ключевое слово GLOBAL в описании объекта приводит к тому, что Турбо Ассемблер делает эту информацию общедоступной. Это поз- воляет вам использовать объект в модуле, отличном от того модуля, в котором он определен. Во все использующие объект модули должно включаться описание объекта. Описание порожденного объекта Объект, который наследует методы и данные другого объекта, называется порожденным (производным) объектом. Вы не можете пере- определить элементы структуры данных порождающего объекта, но мо- жете переопределить отдельные методы, задавая их в списке методов нового объекта. Независимо от того, является ли объект базовым или порожден- ным от другого объекта, его может наследовать другой объект. Нас- ледуемый объект называется порождающим (родительским) объектом. Порожденный объект наследует данные и методы порождающего объек- та, поэтому вам следует использовать наследование только в тех случаях, когда эти методы и данные полезны для нового объекта. Например, вы можете определить объект очереди, который нас- ледует объект связанного списка (поскольку очередь можно реализо- вать в виде связанного списка). Приведем пример такого порожден- ного объекта: queue STRUC GLOBAL list METHOD { init:DWORD=queue_init virtual insert:word = queue_insert ; (процедура вставки ; узла очереди) virtual remove:word = queue_delete ; (процедура удаления ; узла очереди) virtual first:word = queue_first ; (процедура первого ; узла очереди) virtual last:word = queue_last ; (процедура послед- ; него узла очереди) virtual enqueue:word = list_append ; процедура постановки ; в очередь virtual dequeue:word = queue_dequeue ; процедура удаления ; из очереди Размещение перед ключевым словом METHOD объекта с именем list (список) указывает Турбо Ассемблеру, что новый объект queue (очередь) наследует методы и данные объекта list. Размещенный в этом месте объект с любым именем будет наследоваться описываемым объектом. Допускается использовать только одно имя (поддерживает- ся только одиночное наследование). Новый объект queue наследует все данные и методы объекта списка list (если вы их не переопределяете). Заметим, что в queue для установки указателя на таблицу виртуальных методов для очере- дей требуется своя собственная процедура init. Наследуемые для очереди описания методов insert, remove, first и last вновь специфицируются в описании, поэтому данные ме- тоды заменяются указанными процедурами. Для очереди описываются два новых метода: enqueue и dequeue. Заметим, что процедура метода для enqueue (добавление в конец очереди) представляет собой то же самое, что присоединение к кон- цу списка. Однако для извлечения из очереди (отмены постановки в очередь) нужна новая процедура, которая называется queue_dequeue. Объект queue не содержит дополнительных данных, отличных от тех, которые наследуются из списка list. Он наследует указатели начала и конца связанного списка, которые для очереди также нуж- ны, поскольку методы связанного списка используются для обслужи- вания очереди.Описание процедуры методa
Процедура метода работает с экземплярами объекта. Они очень напоминают библиотечные подпрограммы, поскольку должны иметь хо- рошо определенный вызов и возвращать в виде интерфейса значение, но знание внутренней организации процедуры метода не требуется. Процедуры метода для объекта должны обеспечивать исчерпываю- щее обслуживание объектов, то есть, они должны быть единственными процедурами, для который разрешен непосредственный доступ к объ- ектам. Кроме того, при построении методов следует использовать принципы абстрактных данных: вы должны иметь возможность вызова процедур метода без необходимости знать о том, как они работают и какую имеют внутреннюю организацию. Что касается всех других аспектов, то вы можете писать про- цедуры методов на любом известном вам языке или интерфейсе, хотя обычно используются соглашения по вызову C++ или Паскаля. Аргу- менты процедур также выбираются по вашему усмотрению. Обычно не- обходимым является один аргумент - указатель на экземпляр объек- та. Некоторые процедуры методов могут потребовать дополнительных параметров. Например, инициализация метода для объекта списка требует просто указатель на объект списка, в то время как метод включения в список требует указатель на список, указатель на но- вый узел для вставки и указатель на узел, включаемый после него. В использовании статических и виртуальных методов есть свои достоинства и недостатки. Статические методы определяются на эта- пе компиляции, а результатом будет непосредственный вызов проце- дуры метода. Это позволяет выполнить вызов быстрее и не требует использования промежуточных регистров (как при использовании вир- туальных методов). Однако, поскольку эти вызовы определяются на этапе компиляции, вызовы статических методов не обладают гиб- костью вызовов виртуальных методов. Вызовы виртуальных методов выполняются непосредственно через реализацию таблицы виртуальных методов объекта. Тот факт, что вы- зов является косвенным, приводит к тому недостатку, что при вы- полнении вызова требуется использование промежуточных регистров (что может усложнить код программы). Однако большим преимуществом является то, что вызовы виртуальных методов определяются на этапе выполнения. Таким образом, вы можете выполнять вызовы виртуальных методов для порожденного объекта с помощью вызова метода общего объекта-"предка". При этом не требуется точно знать, с каким ви- дом объекта-потомка вы имеете дело. Описание процедур статических и виртуальных методов в точ- ности совпадает с описанием любой другой процедуры, но имеется следующее исключение: если для виртуального метода вы опускаете имя процедуры, то в таблице виртуальных методов создается пустая неинициализированная ячейка, и Турбо Ассемблер не выводит вам ни- каких предупреждений. Если метод не является виртуальным, то про- пуск имени процедуры является ошибкой, поскольку не виртуальные методы не включаются в таблицу. Приведем пример процедуры метода: ; Построение объекта связанного списка. ; Это метод-"конструктор". ; Этот метод должен быть статическим. ; При возврате DX:AX указывают на объект связанного списка, ; в противном случае это ноль. ; Объект выделяется, но пока не инициализируется. list_construct PROC PASCAL FAR USES ds ; -- Выделение объекта связанного списка -- ;; << выполнение выделения >> ret ENDPТаблица виртуальных методов
Таблица виртуальных методов (ТВМ) представляет собой табли- цу адресов процедур, которые выполняют виртуальные методы. Обычно данная таблица размещается в сегменте данных программы. Любой объект, содержащий виртуальные методы, требует, чтобы где-либо в программе находился экземпляр таблицы виртуальных методов. Для создания экземпляра таблицы виртуальных методов объекта используется директива TBLINST. Поскольку эта директива создает таблицу для последнего описанного объекта, ее следует помещать непосредственно после описания объекта, например: INCLUDE list.aso DATASEG TBLINSTИнициализация таблицы виртуальных методов
Чтобы вы могли вызывать виртуальные методы, простой реализа- ции таблицы виртуальных методов недостаточно. Каждый объект с виртуальными методами включает в свои структуры данных указатель на таблицу виртуальных методов. Когда вы создаете экземпляр объ- екта этот указатель нужно инициализировать. Для этой цели можно использовать директиву TBLINST. Инициализируйте указатель таблицы виртуальных методов в ме- тоде init следующим образом: ; Инициализация объекта связанного списка ; Это метод "init" ; Этот метод должен быть статическим. list_init PROC PASCAL FAR ARG @@list:dword USES dx,bx lds bx,@@list ; -- По адресу ds:bx инициализировать таблицу виртуаль- ; ных методов. TBLINIT ds:bx ; -- Инициализировать данные объекта ;; << Здесь инициализируются все данные объекта >> ret ENDP Примечание: Заметим, что метод init должен быть стати- ческим, так как виртуальный метод экземпляра объекта вы вы- зывать не можете, пока не инициализирован указатель таблицы виртуальных методов.Вызов метода объекта
Для вызова метода объекта используйте инструкцию CALL. Для вызова процедур методов Турбо Ассемблер обеспечивает расширение стандартной инструкции CALL - CALL.METHOD. Примечание: Синтаксис инструкции CALL для вызова ста- тических и виртуальных методов совпадает.Вызов статического метода
При вызове процедуры метода, даже если вы знаете, что вызы- вается статический метод, следует записывать инструкцию CALL.METHOD, как если бы вы вызывали виртуальный метод. Это позволит при вызове статических методов избежать нежелательных эффектов и предоставляет вам гибкость - возможность изменять ста- тические методы на виртуальные и обратно без необходимости измене- ния вызова метода. По этой же причине имеет смысл выбирать проме- жуточные регистры вызова, даже если вы знаете, что вызываемый вами метод является статическим. Вызовы статических методов определяются на этапе компиляции и преобразуются в непосредственный вызов нужной процедуры метода объекта. Однако при выполнении вызова не следует вызывать проце- дуру метода непосредственно. Вместо этого используйте расширенную инструкцию CALL.METHOD. В следующей таблице показан пример вызова статического мето- да init для объекта связанного списка: CALL foolist METHOD list:init pascal,ds offset foolist CALL es:di METHOD list:init pascal,es di Сам адрес вызова является адресом экземпляра объекта. Этот адрес используется только по синтаксическим причинам. Фактически генерируемым адресом является непосредственный вызов процедуры метода. В данном примере первым вызовом является вызов метода init объекта list. Так как это статический метод, вы выполняете непос- редственный вызов процедуры метода list_init. Турбо Ассемблер иг- норирует экземпляр объекта foolist (он только передается в ка- честве аргумента процедуре метода). За именем вызова следует обычный расширенный параметр языка и список параметров. Язык и параметры зависят от вызываемого вами метода. Один из параметров обычно является указателем на экземп- ляр объекта. В данном примере метод воспринимает один параметр, являющийся указателем на экземпляр объекта.Вызов виртуального метода
Любой вызов виртуального метода требует косвенного вызова процедуры метода. Для этого используйте расширенную инструкцию CALL.METHOD. Для выполнения вызова Турбо Ассемблер генерирует следующие инструкции: 1. Загружает промежуточные регистры указателем на ТВМ из эк- земпляра объекта. 2. Выполняет косвенный вызов соответствующего элемента таб- лицы. Таким образом, когда вы задаете: CALL <экземпляр> METHOD <объект>:<метод> USES <seg>:<reg> <вызов_проц> то генерируются следующие инструкции: MOV <рег>, [<экземпляр>.<указатель_ТВМ>] CALL [(<сегм><рег>).<метод>] <вызыв_проц> Первая инструкция загружает выбранный регистр <рег> адресом таблицы виртуальных методов из поля указателя ТВМ структуры объ- екта. Вторая инструкция выполняет косвенный вызов соответствующе- го метода в таблице. Например, вызов в виде: CALL es:di method list:insert uses ds:bx pascal,es di,es dx,es cx генерирует последовательность вида: mov bx,[es:di.@Mptr_list] CALL [ds:bx.insert] pascal,es di,es dx,es cx Заметим, для объектов, описанных с таблицами NEAR, инструк- цией CALL.METHOD будет загружаться только регистр смещения. Сегментный регистр всегда должен содержать корректное значение. В следующем примере показано, как обеспечить правильную установку сегментного регистра: ; Добавить узел к концу объекта связанного списка. ; Это виртуальный метод "list_append". list_append PROC PASCAL NEAR ARG @@list:dword,\ @@new:dword USES dx,bx, es,di mov ax,@Data mov ds,ax les di,@@list sub ax,ax CALL es:di method list:insert uses DS:bx pascal, es di,@@new,ax ax ret ENDP Примечание: Пока вы не инициализируете в данных объекта указатель таблицы виртуальных методов, ни один виртуальный метод вызвать нельзя. Это вызвано тем, что указатель загру- жает адрес ТВМ (из которой извлекается адрес нужной проце- дуры виртуального метода). Таким образом, если вы не иници- ализировали указатель на таблицу виртуальных методов, любой вызов виртуального метода приведет к вызову по некоторому случайному адресу. В качестве другого примера рассмотрим базовый объект node (узел), который вы можете включить в любой объект, помещенный в связанный список или очередь. node STRUC GLOBAL METHOD { construct:dword = node_construct ; подпрограмма ; конструктора узла destroy:dword = node_destroy ; подпрограмма ; деструктора узла init:dword = node_init ; подпрограмма ; инициализации узла deinit:dword = node_deinit ; подпрограмма ; деинициализации узла routine virtual next:word = node_adv ; подпрограмма ; следующего узла virtual prev:word = node_back ; подпрограмма ; предыдущего узла virtual print:word = node_print ; подпрограмма ; содержимого узла } node_next dd ? ; указатель следующего ; узла node_prev dd ? ; указатель ; предыдущего узла ends Чтобы можно было использовать связанный список или очередь, вы можете определить любое число объектов, наследующих объект node. Приведем два примера: mlabel STRUC GLOBAL node METHOD { virtual print:word = label_print } label_name db 80 dup (?) label_addr db 80*2 dup (?) label_city db 80 dup (?) label_state db 2 dup (?) label_zip db 10 dup (?) ENDS book STRUC GLOBAL node METHOD { virtual print:word = book_print } book_title db 80 dup (?) book_author db 80 dup (?) ENDS В следующем примере вы для объектов label и book вызываем методы путем вызова printit. Если "предком" является node, не важно, какой объект передается printit. Так как метод печати - это виртуальный метод, вызов выполняется косвенно через ТВМ объ- екта. При первом вызове printit, так как мы передаем экземпляр объекта label, вызывается процедура метода label_print. При вто- ром вызове printit вызывается процедура метода book_print, пос- кольку мы передаем экземпляр объекта book. Заметим, что если бы метод print был статическим, то при вызове node_print всегда вы- зывалась бы процедура node_print (что нежелательно). call printit pascal,<<адрес экземпляра объекта label>> call printit pascal,<<адрес экземпляра объекта book>> . . . printit proc pascal near arg @@obj:dword uses ds,si,es,bx mov ax,@data mov es,ax lds si@@obj call ds:si method node:print uses es:bx pascal,ds si ret endpВызов виртуальных методов "предков"
Благодаря тому, что вы повторно сможете использовать исход- ный код, применение виртуальных методов "предков" может помочь вам записывать методы порожденных классов. Например, пока вы не зададите, является элемент очередью или списком, для очереди мож- но использовать те же методы вывода, что и для списка. В классе списка вы можете записать: virtual show:word = list_show а в классе очереди: virtual show:word = queue_show Подпрограмма list_show может печатать LIST SHOW: с последую- щим выводом отдельных элементов списка. Однако в порожденном классе, если queue_show использует подпрограмму печати, она долж- на печатать собственный заголовок QUEUE SHOW: и использовать list _show только как механизм последовательного прохода по списку и печати отдельных элементов. list_show может определить передавае- мый ей тип структуры и в зависимости от этого печатать заголовок списка. Если подпрограмма для list_show посмотрит на указатель таблицы виртуальных методов передаваемой ей структуры, она сможет определить, совпадает ли указатель с указателем, установленным в подпрограмме list_init для списков (или они различны). Если ука- затель ТВМ в структуре не указывает на таблицу виртуальных мето- дов для списков, то вероятно структура является порожденным ти- пом. list_show может выполнить эту проверку с помощью следующих операторов: cmp [([es:di]).@mptr_list],offset @TableAddr_LIST jne @@not_a_list ; пропустить печать заголовка списка ; Если мы попали сюда, то это список, и следует ; распечатать его заголовок. . @@not_a_list: ; Теперь вывести отдельные элементы списка. Как можно вызвать класс списка и метод вывода из подпрограм- мы queue_show? Если бы вы вызвали list_show непосредственно, то в подпрограмме могла бы возникнуть проблема, если имя используемого для вывода метода изменилось. (Вы можете не помнить об изменениях в вызове queue_show.) Если в queue_show вы поместите следующий оператор: call(es:di) method list:show то получите бесконечный цикл, поскольку, хотя список задан как класс, для которого вызывается метод вывода, так как метод вывода является виртуальным, будет использоваться ТВМ. Поскольку ТВМ для структуры указывала бы на queue_show, вы вернулись бы к той же подпрограмме. Наилучшим способом вызова метода класса является следующий: call +@table_list | show Поскольку при описании класса list_show было задано как зна- чение элемента вывода @table_list, Турбо Ассемблер автоматически транслирует данный оператор в непосредственный вызов list_show. Заметим, что хотя в списке метод вывода описывается как виртуаль- ный, задание вызова приводит к тому, что Турбо Ассемблер выполня- ет непосредственный вызов без просмотра ТВМ. Примечание: Виртуальные подпрограммы обычно вызываются косвенным образом через просмотр ТВМ. В том случае, если вам нужно использовать для вывода элемен- тов класса ТВМ (например, некоторые подпрограммы инициализации могут изменить элемент вывода в таблице, и, в зависимости от то- го, какое устройство используется для вывода всех элементов клас- са, он будет указывать на другую подпрограмму), можно использо- вать следующие операторы (которые работают для вывода элементов класса с таблицей виртуальных методов): mov bx,offset @TABLEADDR_LIST call [(@table_list ptr es:bx).SHOW] Это аналогично последовательности инструкций, которые Турбо Ассемблер использует для выполнения косвенного вызова через ТВМ.Кое-что еще о вызове методов
Часто может встречаться ситуация, когда необходимо вызвать метод порождающего объекта из процедуры метода порожденного объ- екта. Для этого также можно использовать оператор CALL.METHOD. Аналогично инструкции CALL.METHOD вы можете использовать расширение инструкции JMP с ключевым словом METHOD. Эта инструк- ция обеспечивает оптимальную рекурсию. См. Главу 13, где об инс- трукциях CALL.METHOD и JMP.METHOD рассказывается подробнее.Создание экземпляра объекта
Чтобы создать экземпляр объекта, вы можете вызвать метод конструктора объекта (который выделяет память для экземпляра объ- екта), или распределить экземпляр объекта в предопределенном сег- менте данных. Вы можете создать экземпляр объекта точно также, как вы соз- даете экземпляр структуры. Рассмотрите, например, следующие эк- земпляры объектов: foolist list () ; экземпляр списка fooqueue label queue queue () ; экземпляр очереди queue (list_head=mynode,list_tail=mynode) ; экземпляр очереди Когда вы создаете экземпляр объекта, вы можете переопреде- лить любые используемые по умолчанию в объекте данные, значения которых определены в описании объекта, переопределив эти значения в скобках. Однако переопределить методы при создании экземпляра объекта нельзя.Программирование с объектами
Хорошо хранить процедуры метода отдельно от описаний метода (в отдельном файле) и отдельно от кода, использующего данный объ- ект. Мы рекомендуем помещать процедуры метода в файл с именем, совпадающим с именем объекта, и расширением .ASM. Например, про- цедуры метода для объекта связанного списка можно поместить в файл LIST.ASM. Файл процедур метода должен включать (с помощью INCLUDE) описания метода из файла .ASO. В конце данной главы показан пример процедур метода объекта списка. Чтобы показать общую структуру файла, приведем фрагмент файла LIST.ASM (его можно найти в примерах на дистрибутивном дис- ке): ;---------------------------------------------------- ;-- Определение объекта связанного списка ----------- ;---------------------------------------------------- MODEL SMALL LOCALS ;** Определить объект связанного списка ** INCLUSE node.aso ;** Создать экземпляр таблицы виртуальных методов ** ;** связанного списка ** DATASEG TBLINST ;** Методы связанного списка ** CODESEG ;;<<все процедуры методов>> В общем случае следует использовать следующую форму объектно -ориентированного программирования в Турбо Ассемблере --------------T-------------------------------------------------¬ ¦ Файл ¦ Содержимое ¦ +-------------+-------------------------------------------------+ ¦<объект>.ASO ¦ INCLUDE <порождающий_объект>.ASO (если он есть),¦ ¦ ¦ GLOBAL описание объекта и директива GLOBAL для¦ ¦ ¦ каждой процедуры методов. ¦ ¦ ¦ ¦ ¦<объект>.ASM ¦ INCLUDE <объект>.ASO содержит директиву TBLINST¦ ¦ ¦ и описание процедур методов, содержит метод init¦ ¦ ¦ c TBLINIT. ¦ L-------------+-------------------------------------------------- Заметим, что вы можете использовать директивы TBLINST и TBLINIT, даже если в объекте нет виртуальных методов. В этом слу- чае никаких действий не выполняется. Таким образом, мы рекоменду- ем вам использовать директивы TBLINST и TBLINIT независимо от на- личия в объекте виртуальных методов. Поместите директиву TBLINST в соответствующий сегмент данных, а TBLINIT - в метод инициализа- ции объекта (который должен быть статическим). Вы должны вызывать этот метод перед использованием всех других методов объекта.Пример из области программирования
На диске с примерами содержится исчерпывающий пример объект- но-ориентированного программирования, в котором используются опи- санные ранее объекты list и queue, а также объект stack. Описан- ный объект node является базовым объектом для всех данных пользователя, записанных в связанном списке, очереди или стеке. Список соответствующих файлов примера приведен в следующей табли- це: Файлы примера объектно-ориентированного программирования Таблица 4.3 -----------------T----------------------------------------------¬ ¦ Файл ¦ Содержимое ¦ +----------------+----------------------------------------------+ ¦ NODE.ASO ¦ Описывает объект node и методы. ¦ ¦ NODE.ASM ¦ Содержит методы объекта node и экземпляр таб-¦ ¦ ¦ лицы виртуальных методов. ¦ ¦ ¦ ¦ ¦ LIST.ASO ¦ Описывает объект list и его методы. ¦ ¦ LIST.ASM ¦ Содержит методы объекта list и экземпляр таб-¦ ¦ ¦ лицы виртуальных методов. ¦ ¦ ¦ ¦ ¦ QUEUE.ASO ¦ Описывает объект queue и его методы. ¦ ¦ QUEUE.ASM ¦ Содержит методы объекта queue и экземпляр¦ ¦ ¦ таблицы виртуальных методов. ¦ ¦ ¦ ¦ ¦ STACK.ASO ¦ Описывает объект stack и его методы. ¦ ¦ STACK.ASM ¦ Содержит методы объекта stack и экземпляр¦ ¦ ¦ таблицы виртуальных методов. ¦ ¦ ¦ ¦ ¦ OOP.ASM ¦ Содержит пример использования этих объектов. ¦ L----------------+-----------------------------------------------
Назад | Содержание | Вперед