Инструменты пользователя

Инструменты сайта


bb:redbook:208

2.8 Введение в методы и свойства

1. Методы и свойства

Методы и свойства уже частично рассмотрены ранее. Методы — это процедуры объектов, а свойства — поля записей, выраженные через объектный подход. Способы объявления полей и свойств примерно одинаковые, и в КП отсутствует грань, когда поле становится свойством, а процедура методом. Такая особенность вызвана тем, что объектно-ориентированный подход, откуда были привлечены понятия свойства и методы, вводят сущность класс. Класс, это по сути контейнер для ограниченного набора данных, и набора подпрограмм, которые непосредственно связаны с данными в этом же контейнере. Класс, как часть языка, необходим только тогда, когда в языке не сформулированы чётко функции структурных единиц. Так, например в python, объявление класса может быть в одном файле, а часть методов от него в другом. Питонисты скажут, что это есть проявление гибкости; на взгляд паскалиста, это есть проявление бардака. Как-то нелогично, что душевая в бане здесь, а переодевалка через дорогу.

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

Внутри модуля в КП всё выглядит как обычный процедурный код. Снаружи модуля всё начинает выглядеть как объект. Вместо слово «объект», конечно, можно говорить «класс», но даже в названии парадигмы ООП — «объектно-ориентированное программирование» слова «класс» нет! Компонентно-ориентированное программирование выходит за рамки простого ООП именно за счёт того, что основной единицей компиляции является модуль. Модуль и ограничивает объект. И такое совмещение (с дополнительными условиями взаимодействия между модулями) и порождает понятие «компонент». Компонент может быть как в виде исходного текста, так и уже в виде скомпилированного модуля, и отличие от динамически связываемой библиотеки («dynamic link library», DLL или «shared library», SL, «shared object», «so» — «разделяемая библиотека», «разделяемый объект») для задействования компонента используется среда КП, а не средства операционной системы, что позволяет осуществлять дополнительный контроль. Но с помощью КП можно создавать и DLL/SO, но надо понимать, что тогда эти дополнительные возможности контроля уже не будут задействованы. По сути, КП предоставляет частичный функционал операционной среды. Более безопасный и более надёжный. Надо понимать, что объемлющая операционная система продолжает сохранять встроенные ошибки, и предельная надёжность системы будет определяться самой ненадёжной частью системы. Существуют операционные системы написанные на «Обероне» и «Активном Обероне». Они предоставляют надёжную и безопасную среду (разумеется, среда не гарантирует безошибочность работы аппаратных средств). [↑]

2. Свойства объекта

Итак, свойства объекта, в первом приближении — это поля записи. Поля могут быть как базовых типов (INTEGER, REAL и т. д.), так и пользовательские типы(например, tString, tPerson и т. д.). Пример того, как это может выглядеть представлен ниже:

TYPE
    tPerson = RECORD (* объект человек *)
        name: ARRAY 128 OF CHAR; (* имя *)
        family: ARRAY 128 OF CHAR; (* фамилия *)
        age: SHORTINT; (* возраст *)
        id: INTEGER; (* уникальный идентификатор *)
        subset: INTEGER; (* счёт в банке *)
        count: REAL (* сколько денег на счёте *)
    END;

В пользовательском типе tPerson использованы массив литер, короткое целое и два целых. В комментариях, на всякий случай приводятся описания полей. Часто бывает удобней, чем смотреть в документацию (хотя, в ББ — это как раз не проблема).

Если пользовательский тип вставить в реальный код, и попытаться скомпилировать, то обратиться к такому типу из другого модуля будет невозможно, так как в КП принято поведение: то, что не обозначено для экспорта — скрыто. Т. е. нужны дополнительные усилия, чтобы струтктура данных могла быть видна извне. Это существенно отличается от поведения других языков, например таких, как Си, С++ или Ассемблер. Директива include в Ассемблере просто считывает программный код «как есть» в текущую позицию, где была встречена эта директива. Все определения, встреченные во включаемом файле — все будут доступны транслятору. Это типичное поведение. В python, введено понятие «пространство имён». Напрямую, все определения из включаемого модуля не импортируются. Импортируется только само имя модуля, а затем используя объектный синтаксис, можно, например, обратиться к процедуре:

import time 			# импорт модуля time
print time.time() # вызов процедуры time() из модуля time

Это более грамотный подход, но в python чтобы скрыть все определения надо сильно постараться, и в конечном итоге, при желании, всё-равно можно обратиться ко всем сущностям (в python так изначально и задумывалось, хотя обоснование такого решения — по принципу «мне так нравится»). А такое поведение на практике неадекватно. Например, непонятно, зачем нужен замок, который закрывает дверь, если очень захотеть, всё-равно можно такую дверь открыть? Например, банковское дело: мало кто захочет хранить деньги в банке, если недоступность сведений о деньгах будет держаться исключительно на общепринятой договорённости, что сведения о деньгах на счёте считаются недоступными. Также может возникнуть ситуация, когда уже есть отлаженный, проверенный код, который уже менять нельзя. Например, программа, управляющая доменной печью, которую нельзя остановить (а доменную печь для выплавки стали на самом деле останавливать нельзя). И вот, какой-нибудь горе-программист, решает: «А мне надо вот тут переделать программу». И случайно вносит ошибку. Все части программы, которые зависят от такого ошибочного объекта работать не смогут!!! Или ещё хуже: ошибка проявится уже во время работы доменной печи. Например, вместо перемещения чана, чан начнёт выливать расплавленную сталь прямо на сталеваров…

Внутри модуля в КП, любая струтктура доступна, как на чтение, так и на запись. Давайте попробуем для начала осторожно разрешить отдельные поля на чтение для внешнего модуля. Например, идентификатор пользователя банковской системы должен быть известен в любом случае. Также при выходе замуж женщины с удовольствием меняют фамилии, а приёмные дети также меняют и отчество. В редких случаях, люди даже могут поменять имя. Код разрещающий чтение части данных представлен ниже:

TYPE
    tPerson* = RECORD (* объект человек *)
        name-: ARRAY 128 OF CHAR; (* имя *)
        family-: ARRAY 128 OF CHAR; (* фамилия *)
        age: SHORTINT; (* возраст *)
        id-: INTEGER; (* уникальный идентификатор *)
        subset-: INTEGER; (* счёт в банке *)
        count: REAL (* сколько денег на счёте *)
    END;
	

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

Оставшиеся поля так и остались недоступны для внешних модулей. На самом деле, незачем знать посторонним, сколько лет клиенту банка, и уж тем более не позволено произвольно снимать деньги, или вообще смотреть, а сколько их там есть. [↑]

3. Методы объекта

Как уже было упомянуто, метод — это процедуры привязанная к данным. Т. е. процедура требует данные определённого типа. И при этом, поскольку такая процедура привязана, она может быть вызвана с нотацией через точку («.»), что частично облегчает понимание текста программы. Давайте положим что-нибудь на счёт:

PROCEDURE (VAR p: tPerson) Accum* (m: REAL), NEW;
BEGIN
    p.count := p.count + m;
    Log.Real(p.count); Log.Ln
END Accum;	

Этот метод (процедура специального вида) принимает параметр (VAR self: tPerson) в качестве скрытой ссылки. В последствие, вызов person.Accum(n) будет выглядеть именно так, где person — есть объект типа tPerson. После имени метода Accum* (который между прочим экспортирован) имеет в качестве аргумента некоторое число денег m. Эта сумма прибавляется к тому, что уже хранится на счёте, после чего новая сумма выводится на экран. Если эта операция выполняется вручную, убедиться, что зачисление прошло — лишним не будет. Ключевое слово VAR имеет в объявлении функции специальный смысл, пока не рассматриваем (существует и другой способ привязки процедуры к типу). Ключевое слово NEW в данном случае, говорит о том, что у объекта tPerson появился новый метод, который ранее не существовал.

C начислением всё в порядке. Давайте теперь немного денег потратим:

PROCEDURE (VAR p: tPerson) Debet* (m: REAL), NEW;
BEGIN
    p.count := p.count - m;
    Log.Real(p.count); Log.Ln
END Debet;	

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

PROCEDURE Start*;
BEGIN
    anonim.Accum(50);
    anonim.Debet(60)
END Start;
компилируется "TestPerson"
Start  теперь в символьном файле   208   532
старый модуль TestPerson выгружен
50.0
-10.0

Списано денег больше, чем есть на счёте. Пополнение счёта также может привести к ошибке (если объект anonim является невероятно богатым человеком; это всё из-за типа REAL). В тоже время, можно зачислить отрицательную сумму. Что в обычной практике — не допускается.

Полный текст программы представлен ниже:

MODULE TestPerson;
(* модуль описывает объект банковского учёта *)

IMPORT Log;

TYPE
    tPerson = RECORD (* объект человек *)
        name-: ARRAY 128 OF CHAR; (* имя *)
        family-: ARRAY 128 OF CHAR; (* фамилия *)
        age: SHORTINT; (* возраст *)
        id-: INTEGER; (* уникальный идентификатор *)
        subset-: INTEGER; (* счёт в банке *)
        count: REAL (* сколько денег на счёте *)
    END;

VAR
    anonim*: tPerson;

PROCEDURE (VAR p: tPerson) Accum* (m: REAL), NEW;
BEGIN
    p.count := p.count + m;
    Log.Real(p.count); Log.Ln
END Accum;

PROCEDURE (VAR p: tPerson) Debet* (m: REAL), NEW;
BEGIN
    p.count := p.count - m;
    Log.Real(p.count); Log.Ln
END Debet;

PROCEDURE Start*;
BEGIN
    anonim.Accum(50);
    anonim.Debet(60)
END Start;

BEGIN
END TestPerson.

(!)TestPerson.Start

В приведённом коде всё ещё сохраняется ошибка со списанием денег. Решить эту проблему предлагается самостоятельно в качестве задания. [↑]

4. Заключение

Тема ООП значительно шире, и одной главой её описать нельзя. Особенности присущие КП будут дополнительно рассмотрены дальше. Но уже сейчас должны быть усвоены основные правила работы с объектами совмещёнными с модулями — компонентами. [↑]

[ ← Назад ] [ Вверх ↑ ] [ Далее → ]

bb/redbook/208.txt · Последнее изменение: 2020/10/29 07:08 (внешнее изменение)