МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИИ Томский политехнический университет ____________________________________________________...
22 downloads
234 Views
275KB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИИ Томский политехнический университет _______________________________________________________
УТВЕРЖДАЮ Декан АВТФ ________________ Мельников Ю.С. “__” ______2002 г.
КЛАССЫ И ОБЪЕКТЫ В OBJECT PASCAL Методические указания к лабораторной работе № 134 по курсу “Программирование и основы алгоритмизации” для студентов направления 550200 “Автоматизация и управление”
Томск - 2002
УДК 681.3 Классы и объекты в Object Pascal. Методические указания к лабораторной работе № 134 по курсу “Программирование и основы алгоритмизации” для студентов направления 550200 “Автоматизация и управление”
Составитель Н.М. Семёнов Рецензент к.т.н., доцент Е.И. Громаков
Методические указания рассмотрены и рекомендованы методическим семинаром кафедры интегрированных компьютерных систем управления “___” ___________ 2002 г. Зав. кафедрой
А.М. Малышенко
ОГЛАВЛЕНИЕ 1 Структурное программирование .................................................... 1.1 Пример с клиентом ............................................................. 1.2 Инициализация клиентов .................................................. 1.3 Получение адреса клиента ................................................ 1.4 Соединение подпрограмм с данными ............................. 2 Определение объектно - ориентированного программирования ............................................................................ 2.1 Объекты ................................................................................. 2.2 Классы ................................................................................... 2.3 Наследование ....................................................................... 2.4 Объектные поля ................................................................... 2.5 Продолжение примера с клиентами ................................ 2.6 Объявление переменных, представляющих экземпляр класса ................................................................. 2.7 Обращение к элементам объекта ...................................... 3 Формы как классы .............................................................................. 3.1 Создание демонстрационной формы ............................... 3.2 Исследование класса формы .......................................... 4 Контрольные вопросы ....................................................................... 5 Дополнительные задания ................................................................. 6 Литература ...........................................................................................
4 4 5 7 8 9 10 11 12 13 14 15 18 19 20 21 24 24 24
КЛАССЫ И ОБЪЕКТЫ В OBJECT PASCAL Цель работы: Познакомить студентов с основными концепциями объектно ориентированного программирования на примере генерирования класса, соответствующего основной форме Delphi. Объектно - ориентированное программирование (ООП) стало естественным способом разработки сложного программного обеспечения с графическим интерфейсом пользователя. Объектно - ориентированные методы - это мощный инструмент, позволяющий разрабатывать сложные системы, устойчивые по отношению к ошибкам и допускающие последующее расширение. ООП основано на объектно - ориентированной модели, для которой объекты являются основными предметами рассмотрения. Объектно - ориентированная модель предоставляет объектам средства взаимодействия друг с другом. Object Pascal - это объектно - ориентированный язык программирования (ООЯП), поскольку он представляет все средства для поддержки объектно ориентированной модели программирования. Object Pascal ещё часто называют гибридным языком, поскольку он не только поддерживает объектно - ориентированный подход к программированию, но и позволяет создавать программы в традиционном структурном стиле. Однако объектный подход сейчас является наиболее распространённым. Основанная на компонентах архитектура Delphi была бы невозможной, если бы не было объектов. Компоненты Delphi являются, по существу, просто специализированными объектами, и их функционирование определяется объектно - ориентированной архитектурой библиотеки визуальных компонентов (Visual Component Library - VCL).
1. Структурное программирование Прежде чем перейти к объектно - ориентированному программированию, может оказаться полезным взглянуть, как решаются типичные программные проблемы с использованием традиционного структурного метода. 1.1. Пример с клиентом Представим себе типичный пример образца данных, возникающих во многих приложениях: клиент. Традиционно информация о клиенте представляется как запись (record) данных, состоящая из нескольких полей, описывающих конкретного клиента: type RCustomer = record FirstName : String [25]; LastName : String [40]; Street : String [80]; City : String [20]; State : String [2]; Zip : String [10]; LastOrder : String [10]; end;
Значение полей FirstName и LastName очевидно. Street обозначает улицу (и номер дома); City, State и Zip относятся к остальной части полного почтового адреса. Поле LastOrder должно содержать дату в виде ‘2003 - 07 - 22’ , обозначающую дату последнего размещения клиентом заказа на Вашей фирме. Использование структуры record подразумевает, что Вы хотите рассматривать информацию о клиенте как единое целое, но с различными внутренними атрибутами: именем, адресом и т.д. Теперь Вы можете описать переменные типа RCustomer следующим образом: var Cust1, Cust2 : RCustomer; ACust : array [1..100] of RCustomer; Теперь у вас есть два экземпляра (переменные, по обычной терминологии Паскаля) типа RCustomer, Сust1 и Cust2, а также 100 других экземпляров, объединённых в структуру массива. Вы можете использовать эти экземпляры RCustomer для хранения информации о своих клиентах. 1.2. Инициализация клиентов Как правило, одна из первых задач - это присваивание значений объявленным переменным (экземплярам, по терминологии ООП). Другими словами, надо провести инициализацию полей таким образом, чтобы они содержали информацию о Ваших реальных клиентах. Эту задачу можно выполнить, написав прямую, бесхитростную программу, заполняющую каждое поле индивидуально: begin ... Cust1.FirstName := ‘John’; Cust1.LastName := ‘Smith’; Cust1.Street := ‘123 Anystreet’; Cust1.City := ‘Anytown’; Cust1.State := ‘CA’; Cust1.Zip := ‘99999 - 8888’; Cust1.LastOrder := ‘ ‘; ... end; Можно написать аналогичный код для всех своих компонентов, но скоро Вы обнаружите, что повторяются всё время одни и те же операции, используя разные значения экземпляра RCustomer. Например, операции по инициализации переменной Cust2 выглядят очень похожими: begin ... Cust2.FirstName := ‘Mary’; Cust2.LastName := ‘Jonson’; Cust2.Street := ‘567 FirstStreet’;
Cust2.City := ‘Anytown’; Cust2.State := ‘NY’; Cust2.Zip := ‘11111 - 2222’; Cust2.LastOrder := ‘ ‘; ... end; Единственно важное отличие этого фрагмента от предыдущего заключено в том, что теперь идет ссылка на Cust2 и соответствующим полям приписываются другие значения. Обычным способом работы с повторяющимися операторами, подобными приведённым, является оформление их в виде подпрограмм и передача конкретного экземпляра переменной и её полей через список параметров. Вы можете инициализировать экземпляр клиента за один шаг, используя подпрограмму Initialize, определённую и реализованную, например, следующим образом: Procedure Initislize ( var Cust : RCustomer; const FirstName, LastName, Street, City, State, Zip :string ); begin Cust.FirstName :=FirstName; Cust.LastName :=LastName; Cust.Street :=Street; Cust.City :=City; Cust.State :=State; Cust.Zip :=Zip; Cust.LastOrder := ‘ ‘; end; Обратите внимание, что Cust является параметром var, и любые изменения его значения будут переданы фактически переменной, указанной при вызове процедуры. Все поля формального параметра Cust передаются в подпрограмму как аргументы. Только переменная LastOrder инициализируется как пустая строка, сообщая о том, что от клиента не поступило ещё ни одного заказа. Вводя подпрограмму, Вы достигаете большей инкапсуляции (скрытие реализации объекта от внешних модулей) деталей инициализации. Теперь Вы можете повторно использовать процедуру Initialize для инициализации различных переменных типа RCustomer. Обратите внимание на то, как изменяется сам подход к инициализации при превращении набора операторов программы в процедуру - от мелких деталей инициализации, которыми Вы должны заниматься каждый раз при инициализации переменной типа RCustomer, Вы переходите к более обобщённой логике инициализации, опуская при этом подробности. Типичный вызов процедуры Initialize выглядит следующим образом:
begin ... Initialize (Cust1, ‘John’, ‘Smith’, ‘123 Anystreet’, ‘Anytown’, ‘CA’, ‘99999 - 8888’); ... end; Переменная, представляющая клиента Cust1, передаётся процедуре вместе с величинами, которые Вы хотите присвоить экземпляру клиента при инициализации. Вызов подпрограммы скрывает детали инициализации. Вам не надо больше обращать внимания на детальную структуру записи при инициализации её значения. Все низкоуровневые аспекты присваивания значений соответствующим полям осуществляются внутри процедуры и скрыты от внешнего взгляда. Подпрограмма Initialize предназначена для типа RCustomer : Вы не можете передать ей другой тип переменной и ожидать разумного результата . Строгий механизм проверки типа в Паскале не позволит Вам передать процедуре переменную типа, отличного от RCustomer. Однако, связь между типом RCustomer и процедурой Initialize не подчёркивается в определении типа. Поэтому можно и не заметить, что Вы должны использовать процедуру Initialize со всеми экземплярами RCustomer. При необъектном подходе возникает такая проблема, как возможность для переменной типа RCustomer существовать в “неопределённом состоянии”, когда она объявлена в блоке var, но ещё не инициализирована. Неудача инициализации экземпляра RCustomer правильным образом может привести к странному поведению программы. Но в определении RCustomer нет механизма, обеспечивающего автоматическую инициализацию экземпляра типа. Выше приведено только несколько примеров тех трудностей, которые легко могут быть преодолены при объектном подходе. 1.3. Получение адреса клиента Другая типичная операция, выполняемая с экземплярами RCustomer - это получение значений их полей, таких как адрес. Вы могли бы манипулировать каждым экземпляром отдельно, получая значения их полей, следующим образом: var Address1, Address2, Address3 : String; begin ... Address1 := Cust1.FirstName + ‘ ‘ + Cust1.LastName; Address2 :=Cust1.Street; Address3 := Cust1.City + ‘ , ‘ + Cust1.State + ‘ ‘ + Cust1.Zip; ... end; Приведённая программа получает адрес клиента Cust1 в виде, подходящем для печати на конверте в три строки: Address1, Address2 и Address3. Как и раньше, поскольку эту операцию придётся повторять для многих клиентов, имеет смысл оформить её в виде отдельной подпрограммы и передавать ей экземпляр клиента в виде параметра, чтобы использовать этот набор операций со
многими различными клиентами. Вы можете достичь этой цели с помощью следующей демонстрационной процедуры GetAddress : procedure GetAddress ( const Cust : RCustomer; var Address1, Address2, Address3 :String ); begin Address1 := Cust.FirstName + ‘ ‘ + Cust.LastName; Address2 := Cust.Street; Address3 := Cust.City + ‘ , ‘ + Cust.State + ‘ ‘ + Cust.Zip; end; Как и ранее, вместо “жёсткого” кодирования переменной конкретного клиента, процедура GetAddress ссылается на общую переменную Cust, передаваемую в качестве параметра. Она передаётся как параметр вида const во избежание копирования значения фактического аргумента в локальный параметр Cust во время вызова. Параметр const обеспечивает обращение к переменной как к ссылке без копирования её значения, и, кроме того, она не может быть случайно изменена внутри процедуры. Параметры Address1, Address2 и Address3 являются выходными параметрами, через которые передаются соответствующие строки адреса. Поэтому они объявляются как параметры var и их изменённые значения могут передаваться из процедуры. Опять, как и ранее, у Вас есть специальная подпрограмма доступа к данным, скрывающая детали реализации. Тем не менее, этот подход требует, чтобы внутренние детали описания записи были открыты, а принадлежность подпрограммы и типа записи RCustomer друг другу остаётся неочевидной. 1.4. Соединение подпрограмм с данными Пример с записями клиентов демонстрирует типичный сценарий для случая необъектного программирования. Здесь данные, запись RCustomer, определены независимо от подпрограмм, которые оперируют с этими данными: Initialize и GetAddress. Ничего в способе определения этих объектов не помогает обнаружить логическую связь между программой и данными. Вы или должны догадаться, какие подпрограммы работают вместе с этими элементами данных, или, что ещё хуже, поддаться искушению обойти подпрограммы, специально разработанные для конкретных типов данных, и начать осуществлять доступ к полям записи напрямую. В последнем случае Вы можете упустить некоторые действия, которые требуется выполнить и которые были определены внутри подпрограммы. Другая часть проблемы с записью RCustomer состоит в том, что её определение “открыто” и её детали подвержены всем видам “злоупотреблений”. Например, ничто не может помешать кому - либо присвоить значение ‘John’ полю LastOrder. Ясно, что поле LastOrder предназначено не для хранения этого значения. Ничто в определении записи не помешает неосторожному программисту перепутать значения полей с возможными причудливыми эффектами, проявляющимися во время работы программы. Заметьте, что данные в этом необъектноориентированном подходе пассивны и не могут активно проверить свою целостность и правильность. Переменные просто
ждут своей очереди для передачи в подпрограммы. Между данными и подпрограммами существует чёткое разделение ролей: подпрограммы активны и выполняют операции с данными, данные статичны и не имеют способа воздействовать на операции, производящиеся с ними. Информация о тесной связи между конкретным типом данных и подпрограммами, которые работают только с этим типом данных, в таком подходе отсутствует. Поскольку обе подпрограммы, Initialize и GetAddress, явно предназначены для работы с переменными типа RCustomer, это видно из реализации этих программ, будет излишним каждый раз указывать, что только экземпляры типа RCustomer могут обрабатываться этими двумя подпрограммами. Обратите внимание, что при каждом обращении Вашей подпрограммы к конкретной записи клиента надо явно передавать ей экземпляр переменной RCustomer в качестве параметра. Что Вам действительно надо в этой ситуации, это способ автоматического объединения экземпляров клиентов с доступными операциями, которые могут выполняться с ними. Объектно - ориентированный подход определяет, что программы и данные, с которыми они оперируют, тесно связаны между собой. Объектно - ориентированная парадигма объединяет данные с соответствующей программой, которая оперирует с этими данными, и, таким образом, создает единое цельное образование: объект.
2. Определение объектно - ориентированного программирования Объектно - ориентированное программирование может быть охарактеризовано следующими утверждениями: ООП использует объекты как строительные блоки. Объект - это активная единица данных. Объект - это комбинация как элементов данных, так и операций с этими элементами, упакованных вместе для удобства использования. Объединение данных и кода называется инкапсуляцией. Каждый объект является представителем (экземпляром) определенного класса. Во время выполнения программы объекты взаимодействуют друг с другом, вызывая методы, которые являются подпрограммами, характерными для определённого класса. Классы связаны друг с другом соотношениями наследования, с помощью которых объекты могут расширяться и описания существующих объектов многократно использоваться при описании новых объектов. Наследование является одним из механизмов, посредством которого один класс объектов может включаться в работу другого класса объектов. Наследование представляет мощный механизм моделирования отношений, существующих в реальном мире. Представители класса могут рассматриваться не только как представители данного конкретного класса, но и как представители любого из классов - предков, таким образом поддерживая полиморфизм. Полиморфизм - это способ действия с набором объектов одного и того же предка за один шаг, без детализации операций с каждым конкретным объектом. Он является также основанием для расширяемости объектно - ориентированных программ, поскольку он предоставляет способ старым программам воспринимать новые типы данных, которые не были определены во время написания программы. Фундаментальным понятием ООП является понятие объекта.
Объект - это комбинация данных и кода. Другими словами, объект, называемый ещё представителем (какого-нибудь класса), - это порция данных, значение которых определяют его текущее состояние, и набор подпрограмм, называемых методами, оперирующих с этими данными и определяющими поведение объекта, т.е. его реакцию на внешние воздействия. Класс - это объединяющая концепция набора объектов, имеющих общие характеристики. Класс определяет общий интерфейс с окружающим миром, посредством которого Вы можете взаимодействовать с отдельными объектами. Все представители данного класса аналогичны друг другу, поскольку они имеют одинаковый интерфейс, т.е. один и тот же набор операций. 2.1. Объекты Концепция объекта является ключом к пониманию объектно ориентированного программирования. Экземпляры объекта, или просто объекты, считаются активными образованиями с определенными обязанностями. Обязанность объекта состоит в определенном “контракте” с окружающим миром по представлению некоторых “услуг”. Термин объект и экземпляр объекта нередко используются вперемежку и обозначают конкретные экземпляры, их воплощения, или образцы, построенные согласно определению класса. Поведение объекта описывается определением его класса: в конце концов объект является представителем какого - либо класса. Экземпляры объекта являются примерами как порции данных, так и источника действий. Порция данных. Как порция данных объект выглядит подобно паскалевской записи record. Он имеет поля, которым присваиваются значения, и эти поля могут быть различного типа. Комбинация значений всех полей объекта определяет состояние объекта. Изменение значения поля изменяет состояние объекта. Источник действий. Как источник действий, или процедурная сущность, объект является набором методов, т.е. подпрограмм, разработанных для операций с полями. Все методы совместно определяют поведение объекта. Активирование метода объекта проявляется в действии, совершаемом объектом. Комбинация двух взаимно дополняющих взглядов делает объекты такими мощными средствами. Они становятся удобным инструментом для абстракций: внутренность объектов рассматривается как “черный ящик”, доступ к которому осуществляется только через определённый интерфейс методов. Только явно объявленные видимыми характеристики объектов становятся сигнатурой, называемой ещё протоколом, объекта. 2.2. Классы Класс, в одном из значений этого термина, обозначает структурированный тип данных в Object Pascal. Класс является описанием того, как будет выглядеть и вести себя его представитель. Можно рассматривать класс как образование, отвечающее за создание
своих новых представителей (экземпляров) с помощью средств, называемых конструкторами. Вы создаете экземпляр, “попросив” соответствующий класс изготовить его и инициализировать, вызвав соответствующий конструктор. Однако класс не отвечает за уничтожение экземпляров, поскольку эту способность поддерживает сам активный экземпляр: экземпляры объекта самоуничтожаются при вызове соответствующего метода. Объявление класса определяет структуру представителей класса и набор допустимых операций, которые могут выполняться с представителями этого класса. Можно объявить класс внутри блока type, используя синтаксис: type ИмяКласса = class ( ИмяРодительскогоКласса) end; Примеры: type TRectangle = class (TObject) . . . end; TDate = class (TNumber) . . . end; После слова class может быть ещё один идентификатор, заключенный в скобки. Этот идентификатор, если он есть, представляет имя родительского класса. Следующий программный пример дает шаблон модуля, содержащего простейшее определение класса: unit Class1; interface type TClass = class end; implementation end. Класс TClass бесполезен сам по себе, но он иллюстрирует начало создания нового определения класса в Object Pascal. Модуль Class1 является простейшим шаблоном, который Вы можете использовать для создания своих собственных объектных классов. Объявление класса - это объявление типа. По существу, это статическое описание представителя класса и его поведения после создания. Вы можете рассматривать его как рецепт создания представителей класса, нечто определяющее характеристики и поведение “живого” объекта. Класс, сам по себе, не “живой” , но он является заготовкой или сценарием для своих представителей. Короче говоря, класс инкапсулирует концепцию данных и действий в единое унифицированное образование. Объявления классов определяют три характеристики объектов: Данные объекта. Атрибуты исходных данных, называемых ещё полями, содержатся в каждом объекте.
Методы объекта. Их еще называют операциями, функциями - элементами или действиями в других языках программирования. Свойства объектов. Высокоуровневые атрибуты данных, которые тесно связаны с соответствующими методами доступа к данным. Интерфейс класса - это набор всех элементов описания класса - полей, методов и свойств. Эти элементы иногда называют особенностями класса. 2.3. Наследование Классы могут быть связаны между собой разными способами. Одна из фундаментальных концепций объектного программирования - это понятие наследования классов, устанавливающее между двумя классами отношения “родитель потомок”. Наследование - это способность одного класса использовать характеристики другого. Наследование использует отношение “вид чего - либо” между двумя классами; например, “собака - вид млекопитающих”, “млекопитающие - вид животных”, “животные - вид живых существ” и т.д. Предок - это класс, предоставляющий свои возможности и характеристики другим классам через механизм наследования. Класс, который использует характеристики другого класса посредством наследования, называется его потомком. Непосредственный предок класса, от которого данный класс прямо происходит, называется родителем. Указатель родительского класса не является обязательным. Если он не приведён, то родителем предполагается класс TObject; если он дан - указанный класс становится родителем. Вы можете объявить класс, происходящий от определённого другого класса, в блоке type указанием родительского класса в скобках после слова class, используя следующий синтаксис: type ИмяКласса = class (РодительскийКласс) end; Пример : TPrinter = class (TOutputDevice) ... end; Вы можете объявить класс, происходящий по умолчанию от класса TObject, в блоке type без указателя на родительский класс, используя следующий синтаксис: type ИмяКласса = class end;
Этот синтаксис определяет класс, являющийся непосредственным потомком класса TObject - прародителя всех классов Object Pascal. Пример: type Trectangle = class . . . end; Вы должны выбрать родительский класс, если он нужен, таким способом, чтобы основное соотношение “вид чего - либо” соблюдалось. Например, Вы не можете сказать: ”Цветной принтер - это вид дисплея”, но Вы можете сказать: “Цветной принтер - это вид выходного устройства”. Производить класс принтеров от класса дисплеев неудачная мысль. Принтер не является видом дисплея. Образование этого класса от гипотетического OutputDevice - хорошая мысль; принтер является выходным устройством и имеет много общих характеристик с другими выходными устройствами. Концептуально каждый представитель класса - потомка является также представителем родительского класса. Если Вы не можете этого с уверенностью утверждать о некоторых классах в своей иерархии наследования, то Вам надо обдумать другую схему наследования. Например, Вы можете сделать так, чтобы два класса происходили бы от общего предка, который создан только для целей использования общих характеристик в этих двух исходных классах. Наследование расширяет возможности в том смысле, что любой класс - потомок наследует практически все ресурсы (методы, поля и свойства) родительского класса и всех предков до самого верхнего уровня иерархии. Установление отношения наследования между двумя классами создаёт тесную связь между ними. Класс - потомок теснейшим образом связан со своим предком. Каждый экземпляр потомка автоматически является представителем всех классов предков. Экземпляры потомка большей частью похожи на экземпляр непосредственного предка. 2.4. Объектные поля Как минимум, в одном отношении объект (тип class) подобен записи в Паскале: объект может считаться набором полей данных, или атрибутов, сгруппированных вместе под одним именем. Наибольшее отличие между записями и классами состоит в том, что записи не могут наследоваться одна от другой и не могут иметь присоединённых процедур. Только классы могут входить в схемы наследования. Только классы определяют поля и связанные с ними подпрограммы (методы). Атрибуты, или поля, экземпляра объекта подобны переменным, за исключением того, что они тесно связаны с другими атрибутами, образуя конкретный экземпляр. Синтаксис описания полей объекта: type ИмяКласса = class(РодительскийКласс) Поле11, Поле12, . . . Поле1N: Тип1;
Поле21, Поле22, . . . Поле2М: Тип2; ПолеХ1, ПолеХ2, . . . ПолеХР: ТипХ end; Пример: TColorRectangle = class Left, Top, Right, Bottom: Integer; Color: TColor; end; Вы можете определять поля данных внутри описания класса способом, аналогичным определению полей записей. Все описания полей определяют формат экземпляра, т.е. “карту” размещения объекта в памяти, указывая относительные размеры полей в порядке их объявления. 2.5 Продолжение примера с клиентами Тип RCustomer был первоначально определён как запись с несколькими полями, соответствующими атрибутам данных. Теперь Вы можете начать преобразовывать первоначальное необъектное приближение в объектно - ориентированную модель. Первый шаг - это определение формата данных объекта, представляющего клиента. Поскольку все поля данных остаются прежними, объектно - ориентированное определение клиента выглядит почти как определение записи. Листинг 1 показывает новое объектно - ориентированное образование TCustomer с полями данных, определёнными ранее. Листинг 1. Формат данных класса TCustomer. ___________________________________________________________________________ 1: 2: 3: 4: 5: 6 7: 8: 9:
unit Cust1; interface type TCustomer = class FirstName : String [25]; LastName : String [40]; Street : String [80];
10: City : String [20]; 11: State : String [2]; 12: Zip : String [10]; 13: LastOrder : String [10]; 14: end; 15: 16: implementation 17: end. ___________________________________________________________________________ Листинг 1 показывает первый шаг в создании объекта клиента: определение атрибутов данных и их формата в экземпляре клиента. Поля данных и их порядок совпадают с прошлым примером с записью. В этом месте отличия между двумя методами минимальны, и преимущества объектно - ориентированного подхода ещё не очевидны. Следует использовать тип - класс в качестве основного при группировании связанных атрибутов данных для представления существующих объектов в бизнесе и науке. Не следует использовать тип - запись для группирования связанных атрибутов в логически целостные элементы - объекты являются более мощными средствами. 2.6 Объявление переменных, представляющих экземпляр класса Простое объявление объектного типа недостаточно для присваивания значений его полям или какого - либо использования. Например, при объявлении типа не выделяется памяти. В отличие от случая с классами при использовании экземпляров записи Вам просто надо ввести переменную этого типа для действительного резервирования области памяти под экземпляр (здесь не учитывается возможность динамического выделения памяти с помощью указателей). Простое же объявление переменной классового типа ещё недостаточно для резервирования памяти под объект. Объекты, в отличие от объявленных в блоке var переменных, являются динамическими образованиями. Подобно другим переменным и Паскале переменные класса должны быть инициализированы перед использованием. Вы можете инициализировать переменную представителя класса вызовом конструктора этого экземпляра. Замечание. Переменная классового типа не является экземпляром класса это просто ссылка на экземпляр. Она должна быть инициализирована во время выполнения посредством вызова конструктора. Вспомните, что Вы можете объявить тип записи следующим образом: type TRecordType = record Field1: Type1; Field2: Type2; Field3: Type3;
end; Для действительного выделения памяти под запись Вы должны объявить переменную введенного типа следующим образом: var MyRecord: TRecordType; Предыдущее объявление достаточно для выделения памяти, обеспечивающей существование переменной и присваивание значений её полям. Память выделяется для экземпляров записи статическим способом, т.е. во время трансляции. С объектами ситуация другая. Не только объявление класса объектов, но и введение переменных этого типа недостаточно для получения действительных экземпляров. Вы должны выполнить дополнительный шаг для инициализации переменной. Например, следующее описание имитирует способ определения переменной записи: 1: unit Unit1; 2: 3: interface 4: 5: uses 6: Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 7: Dialogs, StdCtrls; 8: 9: type 10: TColorRect=class 11: FLeft, FTop, FRight, FBottom: Integer; 12: FColor: TColor; 13: end; 14: 15: TForm1 = class(TForm) 16: end; 17: 18: var 19: Form1: TForm1; 20: ARect: TColorRect; 21: 22: implementation 23: 24: {$R *.dfm} 25: 26: begin {секции инициализации} 27: ARect:=TColorRect.Create; 28: ARect.FLeft:=10; 29: MessageDlg('Hi',mtInformation,[mbOK],0); 30: end.{модуля и секции инициализации}
В строках со 10 по 13 объявляется класс, представители которого содержат четыре поля типа Integer: FLeft, FTop, FRight и FBottom. Они также содержат поле FColor типа TColor. TColor - стандартный тип, предоставляемый модулем Classes для обозначения цвета простым числом размера LongInt (32 бита). Проблема с объявлением переменной в строке 20 состоит в том, что она ещё не содержит ни одного экземпляра TColorRect. Если Вы попытаетесь обратиться к полю этого объекта с помощью оператора, который обычно работает с полями записи, т.е. Вы напишите без строки 27 ARect.FLeft :=10; в строке 28 - попытка будет безуспешной; появится ошибка выполнения, и программа прекратит работу. Переменная ARect - это ссылочная переменная, и она не инициализирована правильным экземпляром. Следовательно, Вы не можете использовать её для ссылок на поля или методы. Переменная потенциально способна хранить ссылку на экземпляр класса TClorRect, но к этому моменту она не содержит допустимой ссылки. Она не является допустимым экземпляром до тех пор, пока ей не будет явно присвоен какой - нибудь экземпляр - обычно посредством вызова конструктора (строка 27). Экземпляры объекта хранятся в памяти динамически, выделение памяти происходит во время выполнения программы. Только небольшое количество памяти выделяется объектной ссылке статически при объявлении переменной типа class. Фактически экземпляр объекта вызывается к жизни во время выполнения. Поскольку переменная класса - это просто ссылка, она не получает места в памяти для значений объектных полей. Процесс динамического конструирования новых экземпляров, если он успешен, приводит к допустимой ссылке на объект. За работу по фактическому созданию правильного экземпляра отвечает специальная подпрограмма класса, называемая конструктором. Конструкторы - это один из возможных видов объектных методов. 2.7. Обращение к элементам объекта Вы можете обращаться к полям данных и методам объекта с помощью той же методики точечной нотации, которая использовалась при обращении к полям записи. Например, объявляя следующим образом конкретный класс TCircle type TCircle = class X, Y, Radius: Integer; . . . constructor Create; procedure Move (NewX, NewY: Integer); end; и соответствующий экземпляр переменной ACircle, инициализированный правильным образом вызовом конструктора
var ACircle: Tcircle; begin . . . ACircle := TCircle.Create; . . . end; вы можете обращаться к его полям следующим образом: begin . . . ACircle.X := 30; ACircle.Y := 25; ACircle.Radius := 17; . . . end; Вы отделяете имена полей от префиксного названия переменной одной точкой. Вы обращаетесь таким способом не только к полям данного типа объекта, но и ко всем полям класса - предка. Например, представьте себе следующее объявление класса, происходящего от TСircle : type TColorCircle = class(TCircle); BorderColor, InsideColor: TСolor; constructor Create; end; Во - первых, должен быть создан допустимый экземпляр типа TColorCircle посредством вызова конструктора Create. После инициализации переменной AColorCircle допустимым экземпляром Вы можете обращаться к её полям: var AColorCircle: TColorCircle; begin . . . AColorCircle := TColorCircle.Create; . . . AColorCircle.BorderColor := clBlack; AColorCircle. InsideColor := clYellow; . . . AColorCircle.X :=30; AColorCircle.Y := 25; AColorCircle.Radius := 17; . . . end;
Как иллюстрируют последние три команды, Вы можете ссылаться на поля, объявленные внутри класса TСircle, поскольку они также являются частью экземпляра TColorCircle. Эти поля наследуются от TСircle, и Вы можете указывать их, как если бы они объявлялись внутри TColorCircle. Надо рассматривать экземпляр как единый, неделимый, структурированный набор всех унаследованных полей и методов вместе с полями и методами, объявленными в данном классе. Вот почему наследование так важно: Вам надо указывать и размещать не все поля в экземпляре, а только те, которые являются новыми на данном уровне наследования. Для доступа к нескольким полям одного объекта по аналогии с записями может использоваться оператор with. Можно определить два вида полей внутри экземпляра класса: прямого доступа и косвенного доступа. Поле прямого доступа - это реальная часть данных, непосредственно встроенная в экземпляр класса. Примером служат целое число и строка. Прямой доступ требует ресурса памяти для хранения каждого экземпляра класса, в точности равного объявленному размеру. Поэтому значения этих полей могут храниться внутри каждого экземпляра класса. Поле косвенного доступа - ссылка на некоторый другой объект, обычно на другой экземпляр объекта. Поле косвенного доступа требует сравнительно немного памяти для хранения ссылки в экземпляре объекта. Ссылка может предоставить доступ к экземпляру объекта произвольной сложности по его собственным правилам.
3. Формы как классы Когда Вы создаёте пустую форму в новом проекте, интегрированная среда Delphi генерирует код. Этот код определяет новый класс, называемый по умолчанию TFform1, который является потомком TForm. Сгенерированный код класса выглядит подобно следующему: type TForm1 = class(TForm) private {private declarations} public {public declarations} end; Ключевые слова private и public, формально ещё не знакомые нам, возникают по умолчанию и делят объявление класса на три раздела: раздел public по умолчанию, раздел private и явный раздел public. Пока просто обратим внимание на сходство между этим объявлением и объявлением пустого класса. Упрощенное описание класса формы может быть представлено как пустая оболочка, унаследованная от формы TForm: type TForm1 = class (TForm) . . end;
Заметим, что даже если объявление класса выглядит просто, Вы уже имеете значительное количество данных и кода, унаследованных от TForm. Инспектор объекта обнаружит несколько свойств, которые определяет даже пустая форма. Эти свойства ведут себя подобно полям данных. Можно, например, установить текущую вертикальную позицию левого верхнего угла формы с помощью оператора Form1.Top:=16; где Form1-переменная, которая автоматически определяется Delphi, создается при выполнении программы во время инициализации и соответствует уникальному экземпляру форы. 3.1. Создание демонстрационной формы Для создания демонстрационной формы выполните следующее: 1. Создайте новый проект с пустой основной формой. Сохраните файл модуля формы под именем CustLst (CUSTLST.PAS, CUSTLIST.FRM) и файл проекта под именем NameList (NAMELIST.DPR). 2. Измените свойство Name формы c Form1 на CustList. 3. Измените свойство Caption формы на ‘Сustomer List’. 4. Разместите в форме компонент ListBox со страницы Standard Палитры компонентов. Измените его Align - cвойство на alLeft. Поле списка переместится внутри рамки окошка и заполнит левую часть формы. 5. Разместите компонент BitBtn со страницы Additional в свободной области формы. После этого измените его свойства Kind на bkCancel, а надпись - на Close (свойство Kind при этом верните в состояние bkCustom, но начертание или растровая картинка на кнопке Cancel при этом останется на месте). 6. Дополнительно разместите в форме три компонента кнопок со страницы Standard Палитры компонентов. Назовите их (свойство Name) ButtonAdd, ButtonDelete и ButtonChange соответственно. Затем задайте для них (свойство Caption) соответствующие надписи: &Add, &Delete, &Change. 7. Разместите поле редактирования (кнопка”ab” страницы Standard) в форме, удалив содержание его свойства Text, чтобы изначально поле редактирования было пусто. После окончания действий форма должна выглядеть примерно так, как показано на рисунке 1. 3.2. Исследование класса формы Пока Вы создавали форму Customer List, Delphi автоматически редактировала код модуля CustList. Нажав кнопку мыши, сделайте окно редактора кода полностью видимым и посмотрите сгенерированный код класса TCustList. Сгенерированный код должен выглядеть так, как он представлен в листинге 2. Как можно увидеть из листинга 2, форма - это пример класса. Форма - это особый объект в том смысле, что она соответствует элементу видимого интерфейса и создается в процессе интерактивной работы путём переноса в неё компонентов в Дизайнере форм. Однако она является классом, подобным любым другим классам: она имеет родителя (TForm) и определяет несколько полей. Пусть шаблон класса TCustList был создан Delphi в то время, когда Вы затребовали новую форму. Затем объявление класса наполнялось содержимым по мере того, как Вы добавляли компоненты внутри разработчика формы. Заметим, что все компоненты были добавлены в определение класса TCustList сразу после заголовка класса в строках 11 - 16. Вы никогда не должны менять описание этой секции
объявления класса формы. Эта область зарезервирована для автоматического генератора кода Delphi. Для дополнения или удаления
Рис. 1. Основная форма программы Customer List. Листинг 2. Класс TCustList, сгенерированный Delphi. ___________________________________________________________________________ 1: unit Custlst; 2: 3: interface 4: 5: uses 6: SysUtils, Windows, Variants, Messages, Classes, Graphics, Controls, 7: Forms, Dialogs, StdCtrls, Buttons; 8: 9: type 10: TCustList = class(TForm) 11: ListBox1: TListBox; 12: BitBtn1: TBitBtn; 13: ButtonAdd: TButton; 14: ButtonDelete: TButton; 15: ButtonChange: TButton; 16: Edit1: TEdit; 17: private 18: { Private declarations } 19: public 20: { Public declarations } 21 end; 22: 23: var 24: CustList: TCustList; 25: 26: implementation 27: 28: {$R *.DFM} 29:
30: end. ___________________________________________________________________________ компонента в этой секции зайдите в Дизайнер формы и выполните необходимые действия там. По мере того, как Вы визуально добавляете компоненты в форму, требуемые поля автоматически добавляются к коду, и когда Вы удаляете компоненты Delphi удаляет поля, которые были ранее добавлены в определение формы. CustList: TCustList
ListBox1: TListBox ButtonDelete: TButton Edit1: TEdit
ButtonChange: TButton BitBtn1: TBitBtn
ButtonAdd: TButton
Рис. 2. Ансамбль объектов формы TCustList. Разделы private и public в описании класса в строках 17 - 20 Delphi оставляет без изменений, и Вы можете определять здесь свои собственные элементы класса. Строки 11 - 16 листинга служат примером класса с несколькими полями. Эти поля представляют компоненты, которые Вы добавили к форме. Технически все эти поля - ссылки на объекты, они действуют подобно ссылочным переменным, посредством которых Вы имеете доступ к компонентам объектов. ListBox1, EditName, BitBth1, ButtonDelete и ButtonChange - это поля косвенного доступа, указывающие на другие экземпляры объектов, которыми форма владеет во время выполнения. Рис. 2 иллюстрирует совокупность объектов (ансамбль объектов) на момент выполнения программы, соответствующий только что созданной форме. Теоретически, поскольку на форме лежит ответственность по созданию и уничтожению объектов, которыми она владеет, Вы должны управлять механизмом создания таких объектов и инициализацией полей форм требуемыми ссылками где - то внутри реализации класса TCustList. Однако, поскольку Delphi создает описания класса формы “на ходу”, она также берет на себя ответственность по управлению ими во время выполнения программы. Делая класс TCustList наследником TForm, Вы получаете гарантию того, что все требуемые действия по инициализации и уничтожению будут происходить автоматически и в нужное время. Блок описания типов в строках 9 - 21 объявляет внутреннюю структуру экземпляра класса . В дополнение ко всем тем полям и свойствам, которые TCustList унаследовал от своего предка TForm, добавлено несколько новых полей. Исходный класс TForm был таким образом расширен, и каждый экземпляр его потомка, TCustList, требует немного больше памяти, чем он бы занимал, если бы Вы не размещали в форме
визуальные компоненты. Каждый экземпляр TCustList теперь содержит дополнительные шесть переменных, или полей, определенных в описании класса. Как Вы уже знаете, просто описание формы TCustList не создает её экземпляров, которые могут быть использованы во время выполнения. Хотя Delphi автоматически объявляет переменную в строке 24, резервируя место для ссылки на экземпляр формы, эта переменная не может быть использована до тех пор, пока что - нибудь не выполнит её инициализацию. Это “что- нибудь” - оператор, находящийся в основном исходном файле проекта, который создает новый экземпляр формы и присваивает ссылку на него экземпляру переменной CustList. Вы можете проверить, что форма действительно создается в основном файле проекта, выделив Вид/ Список модулей/ NameList. В конце появившегося файла источника Вы увидите код: begin Application.Initialize; Application. CreateForm (TCustList, CustList); Application. Run; end. Второй оператор после начального begin - это вызов процедуры, присваивающей переменной CustList ссылки на созданный экземпляр класса TCustList. Заметьте, что пока программа ничего не делает. Вы только создали видимую конструкцию для части интерфейса пользователя.
4. Контрольные вопросы 1. Для каких программ наиболее эффективно применение технологии объектно - ориентированного программирования? 2. Каковы основные преимущества ООП по сравнению с обычным структурным программированием? 3. Чем определяется состояние объекта (экземпляра объекта)? 4. Чем отличается переменная классового типа от экземпляра класса? 5. Какова технология удаления строки 14: ButtonDelete: TButton; в листинге 2?
5. Дополнительные задания 1. Определите структуру данных для класса TDate, хранящего дату. Определите интерфейсные методы, которые возвращают год, месяц и день по отдельности. Также определите методы доступа, которые могут устанавливать как некоторые, так и все сразу элементы экземпляра класса. Всю работу попробуйте пропустить через транслятор Object Pascal. 2. Определите структуру объекта TInvoice (invoice -накладная). Не забудьте обеспечить возможность для накладной ссылаться на родственный класс TCustomer, определённый в этой лабораторной работе (стр. 15). Результат выполнения задания попробуйте оттранслировать на Object Pascal.
6. Литература 1.Возневич Э. Delphi. Освой самостоятельно. - М.: Восточная Книжная Компания, 1996. - 736 с. 2. Рубенкинг Н. Программирование в Delphi для “чайников”. -К.: Диалектика, 1996. - 304 с. 3. Фаронов В. Delphi 6: учебный курс. – СПб.: Питер, 2002. – 512 с.
КЛАССЫ И ОБЪЕКТЫ В OBJECT PASCAL Методические указания Составитель Николай Михайлович Семёнов Подписано к печати Формат 60*84/16. Бумага писчая №2. Плоская печать. Усл. печ. л. . Уч. - изд. л. . Тираж экз. Заказ № . Бесплатно. Ротапринт ТПУ. 634034, Томск, пр. Ленина,30