SNK Software
Web Studio Монополия Metaproducts Утилиты Игры
Монополию Web Studio Библиотека
Вебмастер Дельфи Работа на ПК Самоучитель
Для PHP Для Delphi
Веб-дизайн Программирование Компьютеры Девайсы Заметки
SNK Software Индустрия hardware Индустрия software
О студии Портфолио Сопровождение сайтов

Новые материалы

Девайсы:
Сравнительный обзор Nokia Lumia 920 и HTC 8X
Девайсы:
Обзор Nokia Lumia 820 – смартфона на WP8
Вебмастеру:
Настройка Apache, PHP и MySQL для Linux-VPS
Вебмастеру:
VPS на домашнем ПК: настройка сети в VM VirtualBox и Debian
Вебмастеру:
VPS на домашнем ПК: устанавливаем Linux Debian 6
Вебмастеру:
VPS на домашнем ПК: установка VM VirtualBox
Работа на компьютере:
Иные возможности текстового процессора Word
Работа на компьютере:
Вставка объектов
Работа на компьютере:
Таблицы в Word
Работа на компьютере:
Печать и сохранение документов
Работа на компьютере:
Сноски, колонтитулы, оглавление и указатели в Word

Изучаем Delphi - Введение в ООП

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

Суть современного программирования

Если в 80-х годах повсеместно применялось, в основном, процедурное программирование, то с приходом в начале 90-х графических сред наподобие Windows, сопровождающееся значительным усложнением структуры и размеров программного кода, возник вопрос использования новых, более прогрессивных средств. Таковыми оказались объектно-ориентированные языки программирования - такие, как C++, а немного позже к ним добавились новые, например, язык Java или полностью переработанные старые - Object Pascal.

ПРИМЕЧАНИЕ
Собственно говоря, язык C++ так же является переработанным вариантом процедурного языка C, только появился он гораздо раньше - в 1982 году. Что касается языка Pascal, то в принципе, первой попыткой переложения его на объектно-ориентрованное применение можно считать такую ветвь, как язык Ada.

Увеличение сложности программ привело не только к появлению новых инструментов, упрощающих разработку, но и повлекло за собой кардинальной пересмотр путей подхода к решению поставленной задачи. Так, если во времена DOS, программист, получив задание, сразу приступал к кодированию, то теперь этому предшествует этап анализа и предварительной разработки. Более того, теперь существует несколько специализаций, причем не всех их можно назвать программистами в привычном смысле этого слова. Это системные аналитики, дизайнеры интерфейса, программные инженеры, собственно "кодеры" и другие. Впрочем, такое многообразие больше характерно для крупных коллективов, решающих объемные задачи. В более простом и прагматичном случае все эти обязанности выполняет один человек.

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

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

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

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

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

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

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

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

Классы и объекты

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

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

Самими же объектами в программе называют не классы, а их экземпляры. Т.е. если мы имеем класс TCar, то его экземпляр, представленный в виде переменной типа TCar (скажем, MyCar), как раз и будет объектом. Чтобы использовать объект, его надо предварительно создать. Делается это при помощи специального метода, который определен для всех классов - Create, называемый так же конструктором класса. Соответственно, чтобы получить объект, являющийся экземпляром класса, мало объявить переменную - надо еще и проинициализировать его при помощи конструктора:

var MyCar: TCar; ... MyCar.Create;

Разумеется, каждый класс может иметь множество своих экземпляров. Скажем, кроме объекта MyCar, могут быть еще BossCar, AnyCar, Truck23, Bus144 и т.д. Все эти переменные называются переменными-экземплярами класса.

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

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

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

Структура класса и видимость

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

type <Имя класса> = class (<Имя родительского класса>)  private   <частные описания>  protected   <защищенные описания>  public   <общедоступные описания>  published   <опубликованные описания> end;

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

type TClass1 = class (TObject) published Property1: integer; end; type TClass2 = class Property1: integer; end;

Что касается области видимости, называемых так же правами доступа то, как следует из синтаксиса определения класса, их может быть 4 вида. Так, разделы private и protected содержат защищенные описания, а public и published - общедоступные. Более тонкие различия между этими категориями заключаются в следующем:

Что касается опубликованных (published) свойств, то эта категория специально создана для библиотеки VCL. Эти те самые свойства, которые отображаются в инспекторе объекта, в остальном они аналогичны публичным свойствам. Все свойства, категория которых в классе явно не задана, считаются опубликованными.

Теперь обратимся собственно к содержимому этих групп. В их качестве могут выступать поля - по аналогии с записями, а так же методы - подпрограммы (как функции, так и процедуры). При этом если имена классов принято начинать с буквы T, то имена полей обычно начинают с буквы F (field). Что касается названий методов, то для их наименования руководствуются теми же принципами, что и при написании обычных подпрограмм, т.е. дают название, характеризующее выполняемое данным методом действие. Для примера создаем класс TUser, содержащий имя учетной записи (login) и пароль (password), а так же некий метод, проверяющий имя и пароль:

type TUser = class fLogin: string; fPassword: string; function Connect: boolean; end;

При реализации метода - в данном случае функции Connect, следует указывать имя класса, к которому она принадлежит. Например, в нашем случае она может выглядеть следующим образом:

function TUser.Connect: boolean; var s,p: string; begin writeln('Input username:'); readln(s); writeln('Input password:'); readln(p); result:= (s=fLogin) and (p=fPassword); end;

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

Кроме полей и методов, существуют собственно свойства (properties), являющиеся чем-то средним: они могут использовать, скажем, поле, для хранения значения, и функцию для его изменения. Фактически, свойства реализуют механизм доступа к полям. В таком случае поля, как правила, делают защищенными (private), а свойства - общедоступными (published). Допустим, в нашем классе TUser, который хранит некую информацию о пользователе, можно реализовать процедуру смены пароля таким образом, чтобы при попытке установки нового пароля он проверялся на допустимость - скажем, подходил по количеству знаков. В таком случае можно создать свойство Password, которое будет обращаться непосредственно к полю fPassword в случае, когда пароль надо просто считать, и вызывать процедуру проверки при попытке изменения этого поля. Попутно создадим свойство Login, но без процедуры проверки. Объявление такого класса может выглядеть следующим образом:

type TUser = class private fLogin: string; fPassword: string; procedure setPassword(newpass: string); published property Login: read fLogin write fLogin; property Password: read fPassword write setPassword; function Connect: boolean; end;

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

При обращении к свойству Password на чтение (read) будет автоматически возвращаться значение, хранящееся в поле fPassword, а при попытке записать (write) в поле новое значение будет вызываться процедура setPassword. Сама эта процедура может выглядеть следующим образом:

procedure TUser.setPassword(newpass: string); begin if Length(newpass)>3 then fPassword:=newpass else writeln('Error! Password is too short!'); end;

Эта подпрограмма проверяет новое значение пароля на длину, и если оно содержит более 3 символов, то присваивается полю fPassword, в противном случае значение поля не меняется, и выводится сообщение об ошибке. В самой же программе обращение к свойству класса выглядит точно так же, как к полю записи, т.е. с использованием доступа через точку:

User.Login:='Serge';

Пример программы, использующей класс TUser, включая описание самого класса вместе со всеми его полями, свойствами и методами, представлен в листинге 7.1 (см. так же Demo\Part2\ClassProp).

Листинг 7.1. Класс со свойствами

program classprop; {$APPTYPE CONSOLE} type TUser = class private fLogin: string; fPassword: string; procedure setPassword(newpass: string); published property Login: string read fLogin write fLogin; property Password: string read fPassword write setPassword; function Connect: boolean; end; procedure TUser.setPassword(newpass: string); begin if Length(newpass)>3 then fPassword:=newpass else writeln('Error! Password is too short!'); end; function TUser.Connect:boolean; var s,p: string; begin writeln('Input username:'); readln(s); writeln('Input password:'); readln(p); result:= (s=fLogin) and (p=fPassword); end; var User: TUser; s,p: string; begin User:=TUser.Create; writeln('Create username'); readln(s); User.Login:=s; writeln('Create password'); readln(p); User.Password:=p; writeln('Username is: '+User.Login); writeln('Password is: '+User.Password); writeln('Try to login...'); while User.Password<>'' do begin if User.Connect then begin writeln('All Ok.'); break; end else writeln('Incorrect data. Try again!'); end; User.Destroy; readln; end.

В начале здесь создается объект User, являющийся экземпляром класса TUser, после чего его полям fLogin и fPassword, посредством соответствующих свойств назначаются значения. При этом неявно задействуются методы write этих свойств. После чего данные считываются, опять-таки через свойства Login и Password, на сей раз будут использованы методы read. После этого, если пароль был задан, начинается цикл, для выхода из которого пользователю надо ввести верные имя и пароль, для чего использован метод Connect.

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

Конструкторы и деструкторы

В самом начале предыдущего примера мы использовали метод Create для создания экземпляра класса TUser, а в конце - метод Destroy для его удаления, хотя сами эти методы определены в классе не были. Но вспомним, что в ООП существует такое понятие, как наследование, а все классы в Delphi происходят от класса TObject. Именно по наследству от TObject классу TUser эти методы и достались.

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

type TUser = class ... constructor Create; end; constructor TUser.Create; begin inherited Create; fLogin:='User'; fPassword:='password'; end;

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

Есть всего одно условие, касающееся расположения конструкторов и деструкторов в объявлении класса: они должны быть объявлены в общедоступной части, т.е. в public или в published.

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

constructor TUser.Create; begin inherited Create; MegaInfo:=TMegaUserInfo.Create; ... end; Что касается метода Destroy, то он призван освободить память, занимаемую экземпляром класса, и называется деструктором (destructor). Соответственно, в том случае, если среди полей класса будут другие объекты, использование переопределенного деструктора обязательно. Например, для класса TUser с полем типа TMegaUserInfo мы получим следующий деструктор: destructor TUser.Destroy; begin ... MegaInfo.Destroy; inherited Destroy; end;

Обратите внимание на то, что если в конструкторе наследуемый метод Create всегда вызывается первым, то в деструкторе наследованный метод Destroy - последним.

Однако просматривая исходные коды других программ (включая библиотеку VCL) вы вряд ли обнаружите вызов метода Destroy напрямую. Дело в том, что к моменту уничтожения того же объекта TUser поле MegaInfo уже может быть освобождено, например, путем присвоения ему в программе значения "nil" (нулевого указателя):

User.MegaInfo:=nil; User.Destroy; // здесь во время выполнения возникнет ошибка! Таким образом, попытка повторно освободить память при помощи метода Destroy приведет к ошибке выполнения программы. Поэтому на практике, вместо метода Destroy всегда используют метод Free, который сначала проверяет, не является ли ссылка на объект нулевым указателем, и если нет - то только тогда вызывает деструктор. Соответственно, вид деструктора и его вызов из программы будут выглядеть таким вот образом: destructor TUser.Destroy; begin ... MegaInfo.Free; inherited Destroy; end; ... User.MegaInfo:=nil; User.Free; // теперь ошибки не будет

Таким образом, использование метода Free является прекрасной возможностью избежать непредвиденных ошибок времени выполнения.

Конструкторы и деструкторы, подобно другим подпрограммам, могут принимать значения в качестве параметров. Например, если при создании нового экземпляра класса TUser нам нужно было бы сразу получить его имя, то конструктор получился бы таким:

constructor TUser.Create(str: string); begin inherited Create; fLogin:=str; fPassword:='password'; end;

Соответственно, создание объекта типа TUser в программе теперь будет выглядеть следующим образом:

User:=TUser.Create('Вася');

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

Методы объектов и оператор with

Конструктор и деструктор вкупе с методом Free - далеко не единственные стандартные методы, достающиеся создаваемым классам в наследство от TObject. Рассмотрим здесь некоторые из них, которые могут быть полезными при работе с объектами в Delphi.

Начнем с того, что для конструктора и деструктора предусмотрены два дополнительных метода, которые вызываются автоматически сразу после создания объекта и непосредственно перед уничтожением. Это AfterConstruction и BeforeDestruction. Хотя к самим этим методам обращаться напрямую нельзя - они вызываются автоматически, допускается определить в них код, который следует выполнить после создания объекта, или перед его удалением.

Если в коде программы требуется выяснить, к какому классу принадлежит тот или иной объект, можно использовать метод ClassName, возвращающий имя класса в виде строки. А если требуется просто проверить, не является ли имя класса у объекта каким-либо конкретным значением, используют метод ClassNameIs:

str := User.ClassName; // в str получим имя класса, например, 'TUser' if User.ClassNameIs('TUser') then ... // условие будет верно, если объект User является экземпляром класса TUser

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

if User is TUser then ... // условие будет верно, если объект User является экземпляром класса TUser

Собственно говоря, другой оператор этой группы - as - так же применим к объектам. Например, если мы хотим использовать переменную User2, являющуюся экземпляром некого класса TCustomUser, в качестве объекта типа TUser, мы можем написать такое выражение:

(User2 as TUser).Login := 'Иван Иваныч'; (User2 as TUser).Password := 'пароль';

Такое выражение будет допустимо, если у того класса, которому принадлежит объект User2 (т.е. TCustomUser), так же имеются свойства Login и Password, причем они должны быть такого же типа, как и у TUser.

Рассмотренные методы иногда бывают полезными при работе с объектами, поскольку позволяют выполнить те или иные операции. А вот оператор with полезен скорее программисту, пишущему программу, поскольку в ряде случаев позволяет существенно сократить размер кода. Например, предыдущие выражения при помощи оператора with можно записать так:

With (User2 as TUser) do begin Login := 'Иван Иваныч'; Password := 'пароль'; end;

Фактически, при помощи оператора with мы можем, единожды указав часть выражения до точки, далее его опускать до тех пор, пока не окончится блок with. При этом не важно, сколько уровней вложения будет использовано. Например, если взять класс TUser, который в качестве поля MegaInfo имеет класс TMegaUserInfo, а оно, в свою очередь, имеет поле Address, являющееся записью типа TAddress, то для обращения к полям записи всякий раз придется указывать довольно-таки длинные строки, определяющие расположение того или иного фрагмента данных:

User.MegaInfo.Address.PostIndex := '119071'; User.MegaInfo.Address.City := 'Москва';

Вместо этого, с использованием оператора with, можно лишь единожды написать весь "путь" к этим данным, в дальнейшем обращаясь лишь к самим полям:

With User.MegaInfo.Address do begin PostIndex := 119071; ... end;

В операторе with допустимо использовать сразу несколько таких путей. Например, если надо будет обращаться и к полям записи Address, и непосредственно к полям User, то можно использовать такой синтаксис:

With User, User.MegaInfo.Address do begin PostIndex := 119071; Login := 'Иван Иваныч'; MegaInfo.Phone := '123-4567'; ... end;

Здесь в первой строке будет использован второй путь - до полей Address, а во второй и третьей - первый, до полей объекта User. Попутно отметим, что оператор with может использоваться не только с классами, но и с обычными записями.

Классы в действии

Попробуем применить инкапсуляцию, наследование и полиморфизм в одном проекте, заодно воспользовавшись оператором with, и посмотрев, как все это работает. Для этого создадим несколько классов, причем один из них будет наследником другого. В качестве родительского класса используем TCustomUser, а класс TUser сделаем его наследником. Еще один класс - TMegaUserInfo будет являться полем класса TCustomUser. Наконец, одним из полей класса TMegaUserInfo пусть будет запись TAddress. В таком случае порядок объявления типов должен производиться в следующей последовательности: TAddress, TMegaUserInfo, TCustomUser, TUser. Таким образом, объявления типов на начальном этапе могут выглядеть таким образом, как показано в листинге 7.2.

Листинг 7.2. Черновое определение типов

type TAddress = record PostIndex: integer; City: string; end; TMegaUserInfo = class Address: TAddress; Phone: string; end; TCustomUser = class protected fRealName: string; public MegaInfo: TMegaUserInfo; end; TUser = class (TCustomUser) private fLogin: string; fPassword: string; procedure setPassword(newpass: string); published property Login: string read fLogin write fLogin; property Password: string read fPassword write setPassword; end;

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

TCustomUser = class protected fRealName: string; public MegaInfo: TMegaUserInfo; function GetRealName: string; constructor Create(username: string); destructor Destroy; override; end;

Теперь займемся дочерним классом TUser. В дополнение к унаследованным от объекта TCustomUser полям и методам, он получит собственные. Пусть это будут свойства Login и Password, вместе с корреспондирующими защищенными переменными. Кроме того, предусмотрим для него конструктор, который будет устанавливать одно из свойств, скажем, Login, в какое-либо значение, определяемое при создании экземпляра класса. В результате мы получим следующее:

TUser = class (TCustomUser) private fLogin: string; fPassword: string; procedure setPassword(newpass: string); published property Login: string read fLogin write fLogin; property Password: string read fPassword write setPassword; constructor Create(username, userlogin: string); end;

Отметим, что по той причине, что конструктор этого класса должен вызывать конструктор родительского, то ему придется принимать сразу 2 параметра - для себя и для "родителя". Зато деструктор в данном случае нам не понадобится, поскольку очистку памяти, занимаемую полем-объектом MegaInfo, выполнит деструктор родительского класса, к которому данное поле и принадлежит. В этом мы убедимся по ходу выполнения программы. Для этого достаточно во всех методах этих классов предусмотреть вывод сообщения, рапортующего о ходе его вызова. Например, метод GetRealName будет выглядеть следующим образом:

function TCustomUser.GetRealName: string; begin result:=fRealName; writeln(' >> Calls TCustomUser.GetRealName'); end;

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

Листинг 7.3. Классы в полном составе

program class2; {$APPTYPE CONSOLE} type TAddress = record PostIndex: integer; City: string; end; TMegaUserInfo = class Address: TAddress; Phone: string; end; TCustomUser = class protected fRealName: string; public MegaInfo: TMegaUserInfo; function GetRealName: string; constructor Create(username: string); destructor Destroy; override; end; TUser = class (TCustomUser) private fLogin: string; fPassword: string; procedure setPassword(newpass: string); published property Login: string read fLogin write fLogin; property Password: string read fPassword write setPassword; constructor Create(username, userlogin: string); end; function TCustomUser.GetRealName: string; begin result:=fRealName; writeln(' >> Calls TCustomUser.GetRealName'); end; constructor TCustomUser.Create(username: string); begin inherited Create; MegaInfo:=TMegaUserInfo.Create; fRealName:=username; writeln(' >> Calls TCustomUser.Create with '+username); end; destructor TCustomUser.Destroy; begin MegaInfo.Free; inherited Destroy; writeln(' >> Calls TCustomUser.Destroy'); end; constructor TUser.Create(username, userlogin: string); begin inherited Create(username); fLogin:=userlogin; fPassword:='password'; writeln(' >> Calls TUser.Create with '+username+' and '+userlogin); end; procedure TUser.setPassword(newpass: string); begin if Length(newpass)>3 then fPassword:=newpass else writeln('Error! Password is too short!'); writeln(' >> Calls TUser.setPassword with '+newpass); end; var User: TUser; begin ... end.

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

User:=TUser.Create('Ivan Ivanovich','Vano');

В результате выполнения этой строки будут выданы 2 сообщения: "Calls TCustomUser.Create with Ivan Ivanovich" и "Calls TUser.Create with Ivan Ivanovich and Vano".

Полный код программы можно найти в каталоге Demo\Part2\Class2. В завершение отмечу, что это, пожалуй, будет последним консольным приложением в Delphi среди примеров этой книги.

Избранное

SNK GSCP
SNK GSCP - новая библиотека для PHP 5!
Web Studio
Web Studio и Visual Workshop
Библиотека:
Стандарты на web-технологии
Монополия
Монополия Android
Загрузки:
скачать программы
Продукция:
программы и книги
Техподдержка / Связаться с нами
Copyright © 1999-2020 SNK. Все права защищены.
При использовании материалов с сайта ссылка на источник обязательна.
Рейтинг@Mail.ru