Глава 9. Объектно-ориентированное программирование

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

  1. Инкапсуляция. Комбинирование записей с процедурами и функциями, манипулирующими полями этих записей, формирует новый тип данных - объект.
  2. Наследование. Определение объекта и его дальнейшее использование для построения иерархии порожденных объектов с возможностью для каждого порожденного объекта, относящегося к иерархии, доступа к коду и данным всех порождающих объектов.
  3. Полиморфизм. Присваивание действию одного имени, которое затем совместно используется вниз и вверх по иерархии объектов, причем каждый объект иерархии выполняет это

действие способом, именно ему подходящим.

Языковые расширения Borland Pascal предоставляют вам все средства объектно-ориентированного программирования: большую структурированность и модульность, большую абстрактность и встро- енную непосредственно в язык возможность повторного использова- ния. Все эти характеристики соответствуют коду, который является более структурированным, более гибким и более легким для обслужи- вания.

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

Дадим хороший совет тому, кто уже имел дело с объектно-ори- ентированным программированием на других языках. Оставьте в сто- роне ваши прежние впечатления об объектно-ориентированном прог- раммировании и изучайте объектно-ориентированные характеристики Borland Pascal в их собственных терминах. Объектно-ориентирован- ное программирование не является единственным путем, оно предс- тавляет собой континуум идей. По своей объектной философии Borland Pascal больше напоминает С++, чем Smalltalk. Smalltalk является интерпретатором, тогда как Borland Pascal с самого нача- ла был чистым компилятором реального кода. Компилятор реального кода выполняет работу иначе (и значительно быстрее), чем интерп- ретатор. Borland Pascal был сконструирован, как инструмент разра- ботки продуктов, а не как инструмент исследования.

Для тех, кто не имеет об этом ни малейшего понятия, мы не будем подробно объяснять, что такое объектно-ориентированное программирование. В этот вопрос и так уже внесено достаточно пу- таницы. Поэтому забудьте о том, что люди говорили вам об объектно -ориентированное программировании. Наилучший способ (и, фактичес- ки, единственный) изучить что-либо полезное об объектно-ориенти- рованное программировании - это сделать то, что вы уже почти сде- лали: сесть и попытаться узнать все самостоятельно.

Объекты

Посмотрите вокруг себя… и вы обнаружите яблоко, которое вы купили к завтраку. Допустим, что вы намерены описать яблоко в терминах программирования. Первое, что вы, возможно, попытаетесь сделать, так это рассмотреть его по частям; пусть S представляет площадь кожуры, J представляет содержащийся в яблоке объем жидко- го сока, F представляет вес фрукта внутри кожуры, D - число семе- чек…

Не думайте таким образом. Думайте как живописец. Вы видите яблоко и вы рисуете яблоко. Изображение яблока не есть само ябло- ко. Оно является символом на плоской поверхности. Но оно не может быть абстрагировано в несколько чисел, каждое из которых располо- жено отдельно и независимо где-нибудь в сегменте данных. Его ком- поненты остаются вместе в их существенной взаимосвязи друг с дру- гом.

Объекты моделируют характеристики и поведение элементов ми- ра, в котором мы живем. Они являются окончательной абстракцией данных.

Примечание: Объекты содержат вместе все свои характе- ристики и особенности поведения.

Яблоко можно разрезать на части, но как только оно будет разрезано, оно больше не будет яблоком. Отношения частей к целому и взаимоотношения между частями становятся понятнее тогда, когда все содержится вместе в одной упаковке. Это называется инкапсуля- цией и является очень важным понятием. Немного позже мы к нему вернемся.

Не менее важным является и тот факт, что объекты могут нас- ледовать характеристики и поведение того, что мы называем порож- дающие, родительские объекты (или предки). Здесь происходит ка- чественный скачок: наследование, возможно, является сегодня единственным самым крупным различием между обычным программирова- нием на Паскале и объектно-ориентированным программированием в Borland Pascal.

Наследование

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

Внутри категории «насекомые» имеется два подразделения: на- секомые с видимыми крыльями и насекомые со спрятанными крыльями или вообще бескрылые. Среди крылатых имеется большее число кате- горий; мотыльки, бабочки, мухи и т.д. Каждая категория содержит большое число подкатегорий, а ниже этих подкатегорий может иметь- ся даже еще большее число подкатегорий (см. Рис. 9.1).

Этот процесс классификации называется таксономией. Это прекрасная начальная метафора для механизма наследования в объектно-ориентированном программировании.

Вопросами, которые задает ученый при попытке классификации некоторого животного или объекта, являются следующие. В чем этот объект похож на другие объекты из общего класса? В чем он отлича- ется от других объектов? Каждый конкретный класс имеет множество свойств поведения и характеристик, определяющих этот класс. Уче- ный начинает с вершины конкретного генеалогического дерева и про- ходит по дочерним областям, задавая себе эти вопросы. Наивысший уровень самый общий, а вопросы самые простые, например, крылатое или бескрылое? Каждый последующий уровень является более специфи- ческим, чем предыдущий, и менее общим. В конце концов, ученый до- бирается до точки подсчета волосков на третьем сегменте задней ноги насекомого - что воистину специфично. (И скорее всего о нем следует сказать, что он не энтомолог.)

                                   +-----------+
                                   | насекомые |
                                   +-+-------+-+
                     +---------------+       |
                     |                       |
                +----+-----+             +---+-------+
                | крылатые |             | бескрылые |
                ++---+---+-+             +-----------+
        +--------+   |   +-------+
    +---+------++----+----++-----+----+
    | мотыльки || бабочки ||   мухи   |
    +----------++---------++----------+

Рис. 9.1 Таксономическая схема насекомых.

Важно помнить то, что если характеристика однажды определе- на, то все категории, расположенные ниже данного определения, со- держат эту характеристику. Таким образом, как только вы определи- ли насекомое, как члена отряда diptera (мухи), то вам не следует отмечать снова, что у мух имеется одна пара крыльев. Разновид- ность насекомых, которую мы зовем мухи, наследует эту характерис- тику от своего отряда.

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

Объекты: наследующие записи

В терминах Паскаля, объект во многом схож с записью, которая является оболочкой для объединения нескольких связанных элементов под одним именем. Предположим, вы хотите разработать программу вывода платежной ведомости, печатающую отчет и показывающую, сколько нужно выплатить каждому служащему за рабочий день. Запись можно организовать следующим образом:

   TEmployee = record
       Name: string[25];
       Title: string[25];
       Rate: Real;
   end;
  Примечание: По  соглашению все типы начинаются с буквы

T. Вы также можете следовать этому правилу.

Здесь TEmployee является типом записи, т.е. шаблоном, ис- пользуемым компилятором для создания переменных типа запись. Пе- ременная типа TEmployee является экземпляром этого типа. Термин «экземпляр» будет вам нередко встречаться в Паскале. Он постоянно применяется теми, кто использует методы объектно-ориентированного программирования, поэтому будет хорошо, если вы начнете мыслить в терминах типов и экземпляров этих типов.

Вы можете оперировать с типом TEmployee двояко. Вы можете рассматривать поля Name, Title и Rate по отдельности, а когда о полях, как о работающих одновременно для описания конкретного рабочего, вы можете рассматривать их совокупность, как TEmployee.

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

   THourly = record
   Name: string[25];
   Title: string[25];
   Rate: Real;
   end;

Вы можете также оказаться несколько догадливее и сохранить тип TEmployee путем создания поля типа TEmployee внутри типа THourly:

   THourly = record
   Worker: TEmployee;
   Time: integer;
   end;

Такая конструкция работает, и программисты, работающие на Паскале, делают это постоянно. Единственное, чего этот метод не делает, так это то, что он заставляет вас думать о том, с чем вы работаете в вашем программном обеспечении. Вам следует задаться вопросом типа; «Чем почасовик отличается от дpугих pабочих?» От- вет прост: почасовик - это pабочий, котоpому платится за коли- чество часов pаботы. Продумайте снова первую часть предложения; почасовик - это pабочий…

Теперь вы поняли!

Запись для pаботника-почасовика hourly должна иметь все за- писи, котоpые имеются в записи employee. Tип THourly является до- черним типом для типа TEmployee. THourly наследует все, что при- надлежит TEmployee, и кроме того имеет кое-что новое, что делает THourly уникальным.

Этот процесс, с помощью которого один тип наследует характе- ристики другого типа, называется наследованием. Наследник называ- ется порожденным (дочерним) типом, а тип, которому наследует до- черний тип, называется порождающим (родительским) типом.

Ранее известные типы записей Паскаля не могут наследовать. Однако Borland Pascal расширяет язык Паскаль для поддержки насле- дования. Одним из этих расширений является новая категория струк- туры данных, связанная с записями, но значительно более мощная. Типы данных в этой новой категории определяются с помощью нового зарезервированного слова object. Тип объекта может быть определен как полный, самостоятельный тип в манере описания записей Паска- ля, но он может определяться и как потомок существующего типа объекта путем помещения порождающего (родительского) типа в скоб- ки после зарезервированного слова object.

В приводимом здесь примере платежной ведомости два связанных типа объектов могли бы определяться следующим образом:

   type
   TEmployee = object
     Name: string[25];
     Title: string[25];
     Rate : Real;
   end;
 
   THourly = object(TEmployee)
      Time : Integer;
   end;
  Примечание: Обратите внимание, что здесь использование

скобок означает наследование.

Здесь TEmployee является родительским типом, а THourly - до- черним типом. Как вы увидите чуть позднее, этот процесс может продолжаться неопределенно долго. Вы можете определить дочерний тип THourly, дочерний к типу THourly тип и т.д. Большая часть конструирования объектно-ориентированных прикладных программ сос- тоит в построении такой иерархии объектов, являющейся отражением генеалогического дерева объектов в приложениях.

Все возможные типы, наследующие тип TEmployee, называются дочерними типами типа TEmployee, тогда как THourly является не- посредственным дочерним типом типа TEmployee. Наоборот, TEmployee является непосредственным родителем типа THourly. Тип объекта (в точности как подкаталог в DOS) может иметь любое число непосредс- твенных дочерних типов, но в то же время только одного непосредс- твенного родителя.

Как показали данные определения, объекты тесно связаны с за- писями. Новое зарезервированное слово object является наиболее очевидным различием, но как вы увидите позднее, имеется большое число других различий, некоторые из которых довольно тонкие.

Например, поля Name, Title и Rate в типе TEmployee не указа- ны явно в типе THourly, но в любом случае тип THourly содержит их благодаря свойству наследования. Вы можете говорить о величине Name типа THourly в точности так же, как о величине Name типа TEmployee.

Экземпляры объектных типов

Экземпляры объектных типов описываются в точности так же, как в Паскале описывается любая переменная, либо статическая, ли- бо указатель, ссылающийся на размещенную в динамической памяти переменную:

   type
     PHourly = ^THourly;
   var
     StatHourly: THourly; { готово }
     DynaHourly: PHourly; { перед использованием память должна
                            выделяться с помощью New }

Поля объектов

Вы можете обратиться к полю объекта в точности так же, как к полю обычной записи, либо с помощью оператора with, либо путем уточнения имени с помощью точки. Например:

   AnHourly.Rate := 9.45;
 
   with AnHourly do
   begin
     Name := 'Sanderson, Arthur';
     Title := 'Word processor';
   end;

Примечание: Не забывайте о том, что наследуемые поля объектов не интерпретируются особым образом только потому, что они являются наследуемыми.

Именно сейчас вы должны запомнить (в конце концов это придет само собой), что наследуемые поля являются столь же доступными, как если бы они были объявлены внутри типа объекта. Например, да- же если Name, Title и Rate не являются частью описания типа THourly (они наследованы от типа TEmployee), то вы можете ссы- латься на них, словно они описаны в THourly:

    AnHourly.Name := 'Arthur Sanderson';

Хорошая и плохая техника программирования

Даже если вы можете обратиться к полям объекта непосредс- твенно, это будет не совсем хорошей идеей. Принципы объектно-ори- ентированного программирования требуют, чтобы поля объектов были исключены из исходного кода, насколько это возможно. Это ограни- чение поначалу может показаться спорным и жестким, но оно являет- ся только частью огромной картины объектно-ориентированное прог- раммирования, которую мы нарисуем в этой главе. Со временем вы увидите смысл, скрытый в этом новом определении хорошей практики программирования, хотя имеются некоторые основания приоткрыть его перед тем, как все придет само. А пока же примите на веру: избе- гайте прямого обращения к полям данных.

Примечание: Borland Pascal позволяет вам делать поля объекта и его методы частными. Подробнее об этом рассказы- вается ниже.

Итак, как же обращаться к полям объекта? Как читать их и как присваивать им значения?

Примечание: Поля данных объекта - это то, что объект «знает», а методы объекта - это то, что объект «делает».

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

Методы

Методы являются одними из наиболее примечательных атрибутов объектно-ориентированное программирования и требуют некоторой практики перед использованием. Вернемся сначала к исходному вопросу о тщетной попытке структурного программирования, связанной с инициализацией структур данных. Рассмотрим задачу инициализации записи со следующим определением:

   TEmployee = object
     Name: string[25];
     Title: string[25];
     Rate: Real;
   end;

Большинство программистов использовали бы оператор with для присвоения полям Name, Title и Rate начальных значений:

   var
     MyEmployee: Employee;
   with MyEmployee do
   begin
       Name := 'Sanderson, Arthur';
       Title :=  'Word processor';
       Rate := 9.45;
   end;

Это тоже неплохо, но здесь мы жестко ограничены одной специ- фическим экземпляром записи - MyEmployee. Если потребуется иници- ализировать более одной записи типа Employee, то вам придется ис- пользовать большее число операторов with, которые выполняют в точности те же действия. Следующим естественным шагом является создание инициализирующей процедуры, которая обобщает оператор with применительно к любому экземпляру типа TEmployee, пересылае- мой в качестве параметра:

   procedure InitTEmployee(var Worker: TEmployee; AName,
                                   ATitle: String; ARate: Real);
   begin
     with Worker do
     begin
       Name := NewName ;
       Title := NewTitle;
       Rate := NewRate;
     end;
   end;

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

Это чувство значит, что, ну, скажем, вы разрабатываете про- цедуру InitTEmployee специально для обслуживания типа TEmployee. Тогда почему вы должны помнить, какой тип записи и какой его эк- земпляр обрабатывает InitTEmployee? Должен существовать некоторый путь объединения типа записи и обслуживающего кода в одно единое целое.

Такой путь имеется и называется методом. Метод - это проце- дура или функция, объединенная с данным типом столь тесно, что метод является как бы окруженным невидимым оператором with, что делает экземпляр данного типа доступными изнутри для метода. Оп- ределение типа включает заголовок метода. Полное определение ме- тода квалифицируется в имени типа. Тип объекта и метод объекта являются двумя лицами этой новой разновидности структуры, именуе- мой методом.

   type
   TEmployee = object
     Name, Title: string[25];
     Rate: Real;
     procedure Init (NewName, NewTitle: string[25];
                                                NewRate: Real);
   end;
 
   procedure TEmployee.Init (NewName, NewTitle: string[25];
                                                NewRate: Real);
   begin
       Name := NewName ;   { Поле Name объекта TEmployee }
       Title := NewTitle;  { Поле Tutle объекта TEmployee }
       Rate := NewRate;    { Поле Rate объекта TEmployee }
   end;

Теперь для инициализации экземпляра типа TEmployee вы просто вызываете его метод, словно метод является полем записи, что име- ет вполне реальный смысл:

   var
     AnEmployee: TEmployee;
     AnEmployee.Init('Sara Adams, Account manager, 15000');
                {пpосто, не так ли?}

Совмещенные код и данные

Одним из важнейших принципов объектно-ориентированного прог- раммирования является то, что программист во время разработки программы должен думать о коде и о данных совместно. Ни код, ни данные не существуют в вакууме. Данные управляют потоком кода, а код манипулирует образами и значениями данных.

Если ваши код и данные являются разделенными элементами, то всегда существует опасность вызова правильной процедуры с невер- ными данными или ошибочной процедуры с правильными данными. Забо- та о совпадении этих элементов возлагается на программиста, и хо- тя строгая типизация Паскаля здесь помогает, самое лучшее, что он может сделать - это указать на несоответствие.

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

Объект осуществляет синхронизацию кода и данных путем сов- местного построения их описаний. Реально, чтобы получить значение одного из полей объекта, вы вызываете относящийся к этому объекту метод, который возвращает значение нужного поля. Чтобы присвоить полю значение, вы вызываете метод, который назначает данному полю новое значение.

Однако, Borland Pascal не вынуждает вас делать это. Как вся- кое структурное программирование, объектно-ориентированное прог- раммирование является дисциплиной, которую вы должны навязать се- бе, используя предоставляемые языком средства. Borland Pascal позволяет вам обращаться к полям объекта непосредственно извне объекта, однако он поощряет вас использовать преимущества объект- но-ориентированного программирования и создавать методы для мани- пулирования полями объекта внутри самого объекта. Borland Pascal позволяет задать принудительную инкапсуляцию с помощью использо- вания описания private в объявлении объекта.

Примечание: Подробнее о принудительной инкапсуляции рассказывается ниже в разделе «Секция private».

Определение методов

Процесс определения методов объектов напоминает модули Borland Pascal. Внутри объекта метод определяется заголовком про- цедуры или функции, действующей как метод:

   type
     TEmployee = object
       Name, Title: string[25];
       Rate: Real;
       procedure Init (AName, ATitle: String; ARate: Real);
       function GetName : String;
       function GetTitle : String;
       function GetRate : Real;
   end;

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

Как и описания процедур и функций в интерфейсной секции модуля (interface), описание методов внутри объекта говорит, что методы делают, но не говорит, как.

Это определяется вне определения объекта, в отдельном описа- нии процедуры или функции. Если метод полностью определяется вне объекта, то имени метода должно предшествовать имя типа объекта, которому принадлежит этот метод, с последующей точкой:

   procedure TEmployee.Init(AName, ATitle: string;ARate: Real);
   begin
     Name := AName;
     Title := ATitle;
     Rate := ARate;
   end;
 
   function TEmployee.GetName: String;
      GetName := Name;
   end;
 
   function TEmployee.GetTitle: String;
   begin
      GetTitle := Title;
   end;
 
   function TEmployee.GetRate: Real;
   begin
      GetRate := Rate;
   end;

Метод опpеделения следует методу интуитивного pазделения точками для указания поля записи. Кpоме наличия опpеделения TEmployee.GetName можно было бы опpеделить пpоцедуpу с именем GetName, в имени котоpой нет пpедшествующего идентификатоpа TEmployee. Однако, такая «внешняя» GetName не будет иметь никакой связи с объектом типа TEmployee и будет только запутывать смысл пpогpаммы.

Область действия метода и параметр Self

Заметьте, что ни в одном из предыдущих примеров конструкция: with объект do… не встречается в явном виде. Поля данных объек- та легко доступны с помощью методов объекта. Хотя в исходном коде поля данных объекта и тела методов разделены, на самом деле они совместно используют одну и ту же область действия.

Именно поэтому один из методов TEmployee может содержать оператор GetTitle := Title без какого-либо квалификатора перед Title. И именно поэтому Title принадлежит тому объекту, который вызывает метод. Если объект вызывает метод, то выполняется неяв- ный оператор with myself do method, связывающий объект и его ме- тоды в области действия.

Неявный оператор with выполняется путем передачи невидимого параметра методу всякий раз, когда этот метод вызывается. Этот параметр называется Self и в действительности является 32-разряд- ным указателем на экземпляр объекта, осуществляющего вызов мето- да. Относящийся к TEmployee метод GetRate приблизительно эквива- лентен следующему:

   function TEmployee.GetRate(var Self: TEmployee): integer;
   begin
     GetRate := Self.Rate;
   end;

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

Но важно ли вам знать о существовании параметра Self? Обычно нет. Генерируемый Borland Pascal код выполняет все это автомати- чески. Однако в некоторых немногочисленных случаях вы можете за- хотеть проникнуть внутрь метода и использовать параметр Self яв- но.

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

Параметр Self является частью физического кадра стека при всех вызовах методов. Методы, используемые как внешние на языке Ассемблера, должны учитывать Self при получении доступа к пара- метрам метода в стеке.

Примечание: Более подробно об использовании методом границ стека рассказывается в Главе 22 «Руководства по язы- ку».

Поля данных объекта и формальные параметры метода

Выводом из того факта, что методы и их объекты разделяют об- щую область действия, является то, что формальные параметры мето- да не могут быть идентичными любому из полей данных объекта. Это является не каким-то новым ограничением, налагаемым объектно-ори- ентированным программированием, а скорее теми же самыми старыми правилами области действия, которые Паскаль имел всегда. Это то же самое, что и запрет для формальных параметров процедуры быть идентичными локальным переменным этой процедуры:

   procedure CrunchIt(Crunchee: MyDataRec,
                                 Crunchby, ErrorCode: integer);
   var
     A, B: char;
     ErrorCode: integer;
   begin
     .
     .
     .

Локальные переменные процедуры и ее формальные параметры совместно используют общую область действия и поэтому не могут быть идентичными. Вы получите сообщение «Error 4: Duplicate identifier» (Ошибка 4; Повторение идентификатора), если попытае- тесь компилировать что-либо подобное, та же ошибка возникает при попытке присвоить формальному параметру метода имени поля объек- та, которому данный метод принадлежит.

Обстоятельства несколько отличаются, так как помещение заго- ловка процедуры внутрь структуры данных является намеком на нов- шество в Турбо Паскале, но основные принципы области действия Паскаля не изменились.

Объекты, экспортируемые модулями

Имеет смысл определять объекты в модуле посредством описаний типа объекта в интерфейсной части модуля, а тела процедур и мето- ды объекта - в секции реализации. Для определения объекта в моду- ле не требуется никаких специальных соглашений.

Примечание: Экспортируемый - означает «определенный в интерфейсной части модуля».

Модули могут иметь свои собственные приватные (частные) оп- ределения типов объектов внутри выполняемой секции, и эти типы подвержены тем же ограничениям, как и всякие другие типы, опреде- ленные в секции реализации. Типы объектов, определенные в интер- фейсной части модуля, могут иметь дочерние типы объектов, опреде- ленные в секции реализации модуля. В том случае, когда модуль B использует модуль A, модуль B также может определять дочерние ти- пы любого типа объекта, экспортируемого модулем A.

Описанные ранее типы объектов и методы можно определить в модуле, как показано в программе WORKERS.PAS на дистрибутивном диске. Чтобы использовать типы объектов и методы, определенные в модуле Workers, вы можете просто использовать этот модуль в своей программе и описать экземпляр типа THourly в секции переменных программы:

   program HourPrt;
 
   uses WinCrt, Workers;
 
   var
   AnHourly: THourly;
     .
     .
     .

Для создания и вывода фамилии pабочего-почасовика, его долж- ности и pазмеpа выплаты, пpедставленной пеpеменной AnHourly, вы просто вызываете методы AnHourly, используя следующий синтаксис:

     AnHourlye.Init('Sara Adams', 'Account manager', 1400);
                           { записывает в экземпляp THourly }
                           { данные для Саpы Адамс: фамилию, }
                           { должность и pазмеp выплаты. }
     AnHourly.Show;

Примечание: Объектами могут быть также типизированные константы.

Объекты, будучи очень схожими с записями, могут использо- ваться внутри оператора with. В этом случае указание имени объек- та, являющегося собственником методов, не является необходимым:

   with AnHourly do
   begin
     Init('Sara Adams', 'Account manager', 1400);
     Show;
   end;

Как и в случаях с записями, объекты могут передаваться в ка- честве параметра процедуре и (как вы увидите позднее) могут раз- мещаться в динамически распределяемой памяти.

Секция private

В некоторых случаях у вас могут иметься части описаний объ- ектов, которые экспортировать нежелательно. Например, вы можете предусмотреть объекты для других программистов, которые они могут использовать, но не могут непосредственно манипулировать с данны- ми объекта. Чтобы облегчить это, Borland Pascal позволяет зада- вать внутри объектов приватные (закрытые) поля и методы.

Приватные поля и методы доступны только внутри того модуля, в котором описан объект. В предыдущем примере, если бы тип THourly содержал приватные поля, то доступ к ним можно было бы получить только в модуле THourly. Даже если другие части объекта THourly можно было бы экспортировать, (части, описанные, как при- ватные, были бы недоступными.

Приватные поля и методы описываются непосредственно после обычных полей и методов, вслед за зарезервированным словом private. Таким образом, полный синтаксис описания объекта будет следующим:

   type
     NewObject = object(родитель)
     поля;                        { общедоступные }
     методы;                      { общедоступные }
   private
     поля;                        { приватные }
     методы;                      { приватные }
   end;

Программирование в "действительном залоге"

Большая часть из того, что говорилось об объектах до сих пор, исходит из удобств и перспектив Borland Pascal, поскольку наиболее вероятно, что это именно то, с чего вы начнете. Теперь начнутся изменения, поскольку мы подошли к концепциям объект- но-ориентированного программирования с помощью некоторых принци- пов программирования на стандартном Паскале. Объектно-ориентиро- ванное программирование имеет свое собственное отдельное множест- во понятий, частично благодаря началам объектно-ориентированного программирования (до некоторой степени ограниченным) в научных кругах, однако также и потому, что эта концепция действительно является радикально отличной от других.

Примечание: Объектно-ориентированные языки однажды метафорично назвали «языками актеров».

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

Было бы полезно представить функцию AnHourly.GetPayAmount как, например, дающую распоряжение объекту AnHourly «Вычислить размер вашей ежедневной платы». Центральной концепцией здесь яв- ляется объект. Этот объект обслуживают как список методов, так и список полей данных, содержащихся в объекте. И ни код, ни данные не являются здесь «директором».

Чтобы быть совсем привлекательным, объект не может быть опи- сан как актер на сцене. Образцу объектно-ориентированного прог- раммирования с большим трудом удается моделировать составляющие проблемы как компоненты, а не как логические абстракции. Случай- ности и закономерности, наполняющие нашу жизнь (от тостеров до телефонных звонков по поводу махровых полотенец) все имеют харак- теристики (данные) и линии поведения (методы). Характеристики тостера могут включать требуемое напряжение, число гренок, кото- рые он может поджарить одновременно, установку слабого или силь- ного уровней поджаривания, цвет тостера, его фабричную марку и т. д. Его поведение может включать загрузку кусков хлеба, поджарива- ние этих кусков и автоматическое выталкивание готовых гренок на- ружу.

Если мы хотим написать программу имитации кухни, то какой же имеется наилучший способ смоделировать различные приспособления, кроме объектов, с их характеристиками и линиями поведения, зако- дированными в поля данных и в методах? Фактически, это уже сдела- но: один из первых объектно-ориентированных языков (Симула-67) был создан как язык для написания таких имитаций.

Есть также причина того, что объектно-ориентированное прог- раммирование довольно крепко связано в традиционном смысле с ори- ентированной на построение графиков средой. Объекты должны быть моделями, и есть ли лучший способ смоделировать объект, чем нари- совать его изображение? Объекты в Borland Pascal должны имитиро- вать компоненты проблему, которую вы пытаетесь разрешить. Примите это во внимание, если в дальнейшем вы намерены эксплуатировать новые объектно-ориентированные расширения Borland Pascal.

 
doc/pascal/user_bp/глава_9.txt · Последние изменения: 2006/06/20 01:11 От romtek
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki