В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 75
Текст из файла (страница 75)
внимание и получило не слишком удачное название "объектно-ориентированное
программирование". Можно надеяться, что читателю теперь понятно
происхождение этого названия (в сущности, программировать в этом стиле
приходится только услуги, предоставляемые объектами тех или иных типов).
Название не слишком удачное, в частности, потому, что такие активные объекты
лучше бы называть "субъектами", так как они полностью "самостоятельно"
определяют свое поведение.
Кстати, остался последний существенный вопрос : определяют на основе
какой информации?
Ясно, что в мире активных объектов информация может поступать только от
других объектов, обращающихся за услугами (в частности, за "услугой" принять
информацию). Так мы приходим к третьему фундаментальному изобретению,
характерному для объектно-ориентированного программирования - концепции
обмена сообщениями между активными объектами, самостоятельно решающими, как
именно реагировать на поступающие сообщения и, в частности, трактовать ли их
как запрос на предоставление услуги другим объектам или просто принять к
сведению.
Упражнение. Нетрудно усмотреть близость к изложенным идеям, например,
концепции Aдовских объектов задачных типов. Самостоятельно сопоставьте эти
концепции (возможно, после более подробного знакомства с объектной
ориентацией). Найдите аналогии и отличия в других известных Вам ЯП.
Итак, в первом приближении мы познакомились с фундаментом объектно-
ориентированного программирования. Пора привести содержательный пример,
демонстрирующий преимущества новой концепции.
19.3. Пример: обогащение сетей на Турбо Паскале 5.5
Напишем все тот же пример с обогащением сетей, но на это раз на ЯП
Турбо Паскаль 5.5 - первой из версий широко известной системы фирмы Борланд,
в которую включены средства объектно-ориентированного программирования.
Имеются в ней и средства раздельной трансляции (появившиеся начиная с версии
4.0). Было заманчиво показать объектно-ориентированное программирование
сразу на примере коммерческой системы. Хотя по сравнению с Адой читатель
почувствует наряду с преимуществами и определенные неудобства от возврата к
ограничениям Паскаля (результат функций не может быть составным, после THEN
- только простой оператор, нельзя повторять имя процедуры после ее
последнего END). Имеются отличия и в концепции модульности (в частности,
спецификация, начинающаяся ключевым словом INTERFACE, отделена от
реализации, начинающейся ключевым словом IMPLEMENTATION, но они не выделены
в отдельные трансляционные модули. Подробнее об этом говорить не будем.
UNIT ПараметрыСети; (* модуль с пустой реализацией *)
INTERFACE
CONST МаксУзлов = 100;
МаксСвязей = 8;
IMPLEMENTATION
END .
UNIT УправлениеСетями;
INTERFACE
USES ПараметрыСети; (* импорт с доступом по коротким именам *)
TYPE
Узел = 1..МаксУзлов;
ЧислоСвязей = 0..МаксСвязей;
Перечень Связей = ARRAY [1..МаксСвязей] OF Узел;
Связи = RECORD
Число : ЧислоСвязей;
Узлы : Перечень Связей;
END;
ЗаписьОбУзле = RECORD
Включен : BOOLEAN;
Связан : Связи;
END;
(* до сих пор - традиционно, но ниже следует объектный тип *)
(* в Обероне его приходилось моделировать *)
Сети = OBJECT
C: ARRAY [1..MаксУзлов] OF ЗаписьОбУзле;
PROCEDURE Инициализировать;
PROCEDURE Вставить (X : Узел); (* у всех процедур - на один *)
PROCEDURE Удалить (X : Узел); (* параметр меньше! *)
PROCEDURE Связать (АУзел, ВУзел : Узел);
PROCEDURE Присвоить (VAR Сеть : Сети);
PROCEDURE УзелЕсть (X : Узел) : BOOLEAN;
FUNCTION ЕстьСвязь(АУзел, ВУзел : Узел) : BOOLEAN;
PROCEDURE ВсеСвязи (X : Узел; VAR R : Связи);
END;
(* среди компонент объектов типа Сети - и обычные поля (данные,
например, поле С), и активные компоненты (операции или правила действий,
например, Вставить) *)
IMPLEMENTATION
PROCEDURE Сети.Инициализация; (* такой операции не было, она и *)
VAR i: 1..МаксУзлов; (* раньше была бы полезной, а при *)
BEGIN (* работе с объектным типом *)
FOR i:= 1 TO МаксУзлов DO (* становится обязательной *)
BEGIN
C[i].Включен := FALSE; C[i].Связан.Число := 0;
END;
END;
(* В объявлениях процедур с префиксом Сети видимы все имена из объявления
этого типа и обращаться к такой процедуре следует как к обычному полю
конкретного объекта типа Сети - примеры будут даны ниже.
Поэтому во всех операциях на один параметр меньше - не нужно передавать в
качестве параметра обрабатываемую сеть. Ведь любая операция работает именно
с той конкретной сетью, которой принадлежит. *)
PROCEDURE Сети.УзелЕсть (X : Узел) : BOOLEAN;
BEGIN
RETURN С[X].Включен; (* доступ короче - работаем в нужной сети *)
END;(* повторять названия в Паскале нельзя - оцените неудобство! *)
(* хотя, конечно, можно применять комментарии *)
PROCEDURE Сети.ВсеСвязи (X : Узел; VAR R : Связи);
BEGIN
R := С[X].Связан;
END;
PROCEDURE Сети.Вставить (X : Узел);
BEGIN
С[X].Включен := TRUE;
С[X].Связан.Число := 0;
END;
PROCEDURE Сети.Присвоить (VAR Сеть : Сети);
BEGIN
С := Сеть.С;
END;
FUNCTION Сети.ЕстьСвязь(АУзел, ВУзел : Узел): BOOLEAN;
VAR i : ЧислоСвязей;
BEGIN
ЕстьСвязь := FALSE ;
WITH C[АУзел].Связан DO
FOR i := 1 TO Число DO
IF Узлы[i] = ВУзел THEN
ЕстьСвязь := TRUE ;
END;
PROCEDURE Сети.Связать (АУзел, ВУзел : Узел);
PROCEDURE Установить_связь(Откуда, Куда : Узел);
BEGIN
WITH C[Откуда] DO (* вставлен контроль *)
IF not Включен THEN write('узел',Откуда,'не включен!');
WITH С[Откуда].Связан DO
BEGIN
IF Число >= МаксСвязей THEN
write('В узле',Откуда,'нет места для связей');
Число := Число+1 ;
Узлы(Число) := Куда ;
END ;
END ;
BEGIN
IF not Есть_связь(АУзел, ВУзел) THEN
BEGIN
Установить_связь(АУзел, ВУзел, ВСети) ;
IF АУзел <> ВУзел THEN (* "<>" - знак неравенства *)
Установить_связь(ВУзел, АУзел) ;
END;
END ;
PROCEDURE Сети.Удалить (X : Узел);
VAR i : ЧислоСвязей;
PROCEDURE Переписать (ВУзле : Узел;
После : ЧислоСвязей)
VAR j : ЧислоСвязей;
BEGIN
j := После;
WITH C[ВУзле].Связан DO
WHILE J < Число-1 DO
Узлы[j] := Узлы[j+1];
j := j+1;
END
END ;
PROCEDURE Чистить (Связь, ВУзле : Узел);
VAR i : ЧислоСвязей;
BEGIN
i := 1;
WITH С[ВУзле].Связан DO
REPEAT
IF Узлы[i] = Связь THEN
BEGIN
Переписать (ВУзле, i);
Число := Число-1;
EXIT;
END; i := i+1;
UNTIL i < Число+1
END ;
BEGIN
С[X].Включен := FALSE; i := 1;
WITH C[X].Связан DO
BEGIN
REPEAT
Чистить (X, Узлы[i]);
i := i+1;
UNTIL i < Число+1;
Число := 0;
END ;
END ;
END .
PROGRAM Клиент; (* головная программа *)
USES УправлениеСетями;
VAR Сеть1, Сеть2 : Сети; (* объявление экземпляров объектного типа *)
BEGIN
Сеть1.Инициализировать; (* работа программы - это работа объектов *)
Сеть2.Инициализировать;
Сеть1.Вставить (33, 13);
Сеть2.Присвоить (Сеть1); (* объект как параметр для другого объекта *)
END Клиент;
UNIT УправлениеСетямиСВесом;
INTERFACE
USES УправлениеСетями, ПараметрыСети;
TYPE
Вес = INTEGER;
СетиСВесом = OBJECT (Сети) (* обогащение аналогично Оберону *)
A: ARRAY [1..МаксУзлов] OF BOOLEAN; (* для процедуры ВесПути *)
B: ARRAY [1..МаксУзлов] OF Вес;
PROCEDURE Вставить (X: Узел; P: Вес);
PROCEDURE Присвоить (VAR Сеть : Сети);
FUNCTION ВесПути (X,Y: Узел): Вес;
END;
END ;
IMPLEMENTATION
PROCEDURE СетиСВесом.Вставить (X : Узел; P: Вес);
BEGIN (* в отличие от Оберона правила видимости *)
C.Вставить (X); (* обеспечивают краткость и защиту других объектов *)
END;
PROCEDURE СетиСВесом.Присвоить (VAR Сеть: Сети);
BEGIN
Cети.Присвоить (Сеть); (* обращение из объекта типа СетиСВесом
к операции, находящейся в его подъобъекте родительского типа Сети *)
А := Сеть.А;
B := Сеть.B;
END;
(* Реализация следующей операции (функции) на Обероне не приводилась.
Поэтому ее не стоит учитывать при сопоставлении удобства программирования на
Турбо Паскале 5.5 и на Обероне. Программа приведена для полноты и в качестве
решения данной ранее задачи. *)
FUNCTION СетиСВесом.ВесПути (X,Y: Узел;): Вес;
VAR i : Узел;
P : Вес;т
PROCEDURE ВП (X, Y: Узел; VAR PR: Вес);
VAR j : ЧислоСвязей;
BEGIN PR := -1;
IF X = Y THEN PR := B[X]
ELSE IF ЕстьСвязь (X, Y) THEN PR := B[X] + B[Y]
ELSE
BEGIN A[X]:=FALSE; (* чтобы не зациклиться при поиске пути *)
WITH C[X].Связан DO
FOR j :=1 TO Число DO
IF А[Узлы[j]] THEN (* рекурсивный вызов ВП *)
BEGIN ВП(Узлы[j], Y, PR);
IF PR >= 0 THEN (* путь найден *)
BEGIN PR:=B[X]+PR; EXIT END;
END;
END;
IF not A[Y] and (PR <= 0) THEN
BEGIN
writeln; write('нет пути между',X,'и',Y);
A[Y] := TRUE; (* чтобы не повторять сообщений при выходе из *)
END; (* рекурсии *)
END;
BEGIN
FOR i:=1 TO МаксУзлов DO A[i] := TRUE;
A[Y] := FALSE;
ВП(X,Y,P);
ВесПути := P;
END;
END .
Итак, действующими лицами в программе становятся активные объекты с
полями-процедурами. Последние принято называть правилами (действий)
объектов, методами, операциями объектного типа. Мы будем употреблять термин
"операция" или "правило". В Турбо Паскале строгого запрета на доступ извне к
обычным (непроцедурным) полям объектов нет, но все готово к тому, чтобы
такой запрет ввести. Естественная инкапсуляция будет полностью обеспечена -
поля объектов нельзя будет испортить (доступ только через операции этого же
объекта, созданные автором рассматриваемого объектного типа). В Турбо
Паскале такой стиль программирования рекомендован, но не обязателен. При
обогащении доступ к старым полям в Турбо Паскале допустим, но с точки зрения
идеала развиваемости вполне можно было ограничиться доступом посредством
старых операций.
Приятно видеть, как программа становится компактнее и прозрачнее за
счет расчистки от загромождения лишними параметрами-сетями и уточнениями.
Аналогичные возможности управления видимостью действуют не только при
определении, но и при использовании объектных типов. Обратите внимание, как
легко разрешается потенциальный конфликт наименований - он возможен только
внутри объектов (но тогда и разбираться с ним могут в принципе сами
объекты, хотя в Турбо Паскале действуют и общие правила, связанные с так
называемыми виртуальными операциями).
19.4. Виртуальные операции
Виртуальные операции Турбо Паскаля 5.5 служат примерами абстрактных
атрибутов. Их конкретизация происходит в контексте обогащенного
(производного) типа и состоит в сопоставлении именам операций конкретных
операций из этого контекста, обладающих теми же названиями. Виртуальные
операции Турбо Паскаля 5.5 не допускают такой глубокой конкретизации, как
атрибуты-поля, поскольку могут быть связаны только с типом, а не с
конкретными экземплярами этого типа.
Один из способов реализации виртуальных операций виден из примера в
Обероне, где операция (например, Присвоить) из контекста, в котором
создается обогащенный объектный тип СетиСВесом, присваивается переменной,
объявленной в том контексте, где объявлен исходный объектный тип (Сети).
Ясно, что аналогично можно присваивать сами операции или указатели на них
конкретным объектам (экземплярам объектного типа) в любом подходящем
контексте.
В Турбо Паскале для аналогичной цели служит Таблица Виртуальных
Операций (ТВО), которую строит компилятор для каждого объектного типа с
виртуальными операциями. Каждый объект такого типа содержит указатель на
ТВО. Так что программист избавлен от необходимости явно программировать
соответствующие объявления процедурных переменных и присваивания, а при