|
|||||||||||||||||||
|
МетодыМетоды - это одни из наиболее привлекательных атрибутов объектно-ориентированного программирования, и они очень выгодны для использования. Вернемся назад к обоснованию старой необходимости структурного программирования, инициализации структур данных. Рассмотрим задачу инициализации записи с таким определением: Location = record X, Y : Integer; end; Большинство программистов будут использовать оператор with для присваивания исходных значений полям X и Y: var MyLocation : Location; with MyLocation do begin X := 17; Y := 42; end; Это работает хорошо, но это тесно связано с одним определенным экземпляром, MyLocation. Если потребуется инициализировать более, чем одну запись Location, Вам нужно будет больше операторов with, выполняющих, по существу, одну и ту же вещь. Следующим естественным шагом является построение процедуры инициализации, которая обобщает оператор with, чтобы включить любой экземпляр типа Location, передаваемый как параметр: procedure InitLocation(var Target : Location; NewX, NewY : Integer); begin with Target do begin X := NewX; Y := NewY; end; end; Это работает, все хорошо - но если у Вас появилось чувство, что это немного более глупо, чем должно быть, то Вы чувствуете такую же вещь, которую нащупывают более ранние теории объектно-ориентированного программирования. Это чувство подразумевает, что Вы спроектировали процедуру InitLocation специально для обслуживания типа Location. Но зачем Вы должны хранить задание типа записи и экземпляра, над которым InitLocation выполняет действия? Должно быть несколько способов объединения типа запись и кода, обслуживающего ее, в единое целое. Здесь рассматривается такой способ. Он называется методом. Метод - это процедура или функция настолько тесно связанные с данным типом, что метод окружается невидимым оператором with, делая доступными экземпляры этого типа изнутри метода. Определение типа включает заголовок метода. Полное определение метода задается с именем типа. Тип объекта и метод объекта - это две стороны этого нового вида структуры, называемой объектом: type Location = object X,Y : Integer; procedure Init(NewX, NewY : Integer); end; procedure Location.Init(NewX, NewY : Integer); begin X := NewX; {поле Х объекта Location } Y := NewY; {поле Y объекта Location } end; Теперь, чтобы инициализировать экземпляр типа Location, просто вызовите его метод, как если бы метод был полем записи: var MyLocation : Location; MyLocation.Init(17, 42); {легко, нет?}
Код и данные вместе.Одним из важнейших принципов объектно-ориентированного программирования является то, что программист должен думать о коде и данных вместе во время проектирования программы. Ни код, ни данные не существуют в вакууме. Данные управляют потоком кода, а код манипулирует формой и значением данных. Когда код и данные существуют отдельно, всегда есть опасность вызова правильной процедуры с неверными данными или неверной процедуры с правильными данными. Проверка этого соответствия является задачей программиста, и в то время, как Паскаль оказывает помощь при проверке строгого соответствия типов, лучшее, что он может сказать, это то, чего нельзя сделать "вместе". Паскаль ничего не говорит о том, что можно сделать "вместе", если этого нет в комментариях или в Вашей голове, то Вы в воле случая. Посредством объединения объявления данных и кода объект помогает хранить их синхронно. Обычно, для того, чтобы получить значение одного из полей объекта, Вы вызываете метод, принадлежащий этому объекту, который возвращает значение требуемого поля. Чтобы установить значение поля, Вы вызываете метод, присваивающий новое значение этому полю. Как и многие аспекты объектно-ориентированного программирования, инкапсуляция данных - это дисциплина, которой Вы всегда должны следовать. Лучший способ обращаться к данным объекта с использованием его методов вместо прямого чтения данных. Turbo Pascal позволяет Вам усилить инкапсуляцию, используя объявление private в объявлении объекта (см. ниже "Раздел private").
Задание методов.Процесс задания метода напоминает модули Turbo Pascal. Вне объекта метод задается заголовком процедуры или функции, действующей как метод: type Location = object X,Y : Integer; procedure Init(InitX, InitY : Integer); function GetX : Integer; function GetY : Integer; end; Примечание: Все поля данных должны быть объявлены перед первым объявлением метода. Как и объявление процедур и функций в интерфейсном разделе модуля, объявления метода внутри объекта говорят, что метод делает, но не как. Как - задается вне определения объекта в отдельном определении процедуры или функции. Когда методы полностью определены вне объекта, имени метода должно предшествовать название типа объекта, которому метод принадлежит, сопровождаемое точкой: procedure Location.Init(InitX, InitY : Integer); begin X := InitX; Y := InitY; end; function Location.GetX : Integer; begin GetX := X; end; function Location.GetY : Integer; begin GetY := Y; end; Определение метода следует интуитивно методу задания поля записи с помощью точки. Вдобавок к определению Location.GetX, будет вполне законно задать процедуру, названную GetX, без предшествующего ее имени идентификатора Location. Однако, "внешняя" GetХ не будет иметь связи с типом объекта Location и может привести к путанице в программе.
Сфера действия метода и Self параметр.Заметим, что нигде в предыдущих методах нет явной with объект do ... конструкции. Поля данных объекта доступны этим методам объекта. Разделенные в исходном коде, тела методов и поля данных объекта на самом деле разделяют одну и ту же сферу действия. Это является объяснением того факта, что один из Location методов может содержать оператор GetY := Y; без квалификатора для Y. Это происходит потому, что Y принадлежит тому же объекту, что и этот метод. Когда объект определяет метод, при этом присутствует неявный оператор with MySelf do метод, связывающий объект и его метод в сфере действия. Этот неявный with оператор выполняется посредством передачи неявного параметра методу каждый раз, когда какой-либо метод вызывается. Этот параметр называется Self и фактически является полным 32 битовым указателем на экземпляр объекта, делающий вызов для метода. GetY метод, принадлежащий Location, грубо эквивалентен следующему: function Location.GetY(var Self : Location) : Integer; begin GetY := Self.Y; end; Примечание: Этот пример синтаксически не совсем правилен; он приводится здесь просто для того, чтобы дать более полную оценку особой связи между объектами и его методами. Важно ли для Вас знать больше о Self? Обычно нет. Генерируемый Turbo Pascal код обрабатывает его автоматически. Однако есть несколько случаев, когда Вы можете вмешаться внутрь метода и сделать явным использование Self параметра. Примечание: Явное использование Self законно, но Вы должны избегать ситуаций, требующих этого. Фактически Self является автоматически объявленным идентификатором, и если Вам случилось обнаружить конфликт идентификаторов внутри метода, Вы можете разрешить его путем использования Self идентификатора в качестве квалификатора к любому полю данных, принадлежащих объекту метода: type MouseStat = record Active : Boolean; X, Y : Integer; LButton, RButton : Boolean; Visible : Boolean; end; procedure Location.GoToMouse(MousePos : MouseStat); begin Hide; with MousePos do begin Self.X := X; Self.Y := Y; end; Show; end; Примечание: Методы, реализованные как внешние на языке ассемблер, должны принимать в расчет Self, когда они получают доступ к параметрам метода в стеке. Более детальное описание структуры стека вызова метода см. главу 18 Руководства программиста. Этот пример слишком прост и использование Self можно избежать путем отказа от использования with оператора внутри Location.GoToMouse. Вы можете оказаться в ситуации, когда внутри сложного метода необходимость использования оператора with упрощает логику в достаточной степени для отказа от использования параметра Self. Self параметр - это часть физической структуры стека для всех вызовов метода.
Поля данных объекта и формальные параметры метода.Следствием того факта, что объекты и их методы разделяют одну и ту же сферу действия, является то, что формальные параметры метода не могут быть идентичны полям данных объекта. Это не новые ограничения, вводимые объектно-ориентированным программированим, а скорее старые правила сферы действия, которые всегда были в Паскале. Это те же самые правила, не позволяющие формальным параметрам процедуры быть идентичными локальными переменными процедуры: procedure CrunchIt(Crunchee : MyDataRec; CrunchBy, ErrorCode : Integer); var A, B : Char; ErrorCode : Integer; {это объявление приведет к ошибке} begin ... Локальные переменные процедуры и ее формальные параметры разделяют одну и ту же сферу действия и поэтому не могут быть идентичными. Вы получите "Error 4: Duplicate identifier", если попытаетесь откомпилировать что-то подобное этому; случится та же самая ошибка, если Вы попытаетесь задать методу формальный параметр, идентичный какому-либо полю в объекте, которому принадлежит этот метод. Последствия немного отличаются, так как помещение заголовка процедуры внутри структуры данных - это новое в Turbo Pascal, но ведущие принципы сферы действия Паскаля в целом не изменились. Предыдущая страница | Следующая страница |
|
Web дизайн: Бурлаков Михаил
Web программирование: Бурлаков Михаил