Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Простой пример. Арифметика рациональных чисел.




PROGRAM\OOP\PRG00\PROJECT2.DPR
НАСЛЕДОВАНИЕ (inheritance) И ПОЛИМОРФИЗМ (polymorphism).

¨ Компоненты (поля, методы и свойства) класса-родителя являются компонентами класса-потомка (descendant). Явное объявление этих компонентов в классе-потомке не требуется, они наследуются от предков (ancestors) по иерархии наследования. Но «видимы» они в соответствии с областью их видимости, которая определена (или унаследована) классом-родителем. Причем важно помнить, что область видимости определяется относительно модуля, в котором определен соответствующий класс-предок.

¨ В классе-потомке можно доопределять новые компоненты, отсутствующие в классе-родителе.

¨ В классе-потомке можно переопределять (redefine, redeclaring) компоненты, унаследованные от класса-родителя. Переопределение полей – инструмент не устоявшийся, ниже этот случай не рассматривается.

¨ Совместимость типов по присваиванию (assignment-сompatibility of class types). Объекту типа класс можно присвоить значение типа подкласс (класс-потомок). Но такой объект «все равно не видит» (казалось бы своих) компонентов, которые были доопределены потомком (а возможно просто не получает их при присваивании). Труднее понять, как такой объект понимает переопределенные потомком методы... в первом приближении – «он не видит отличий, т.к. его взгляд абстрактнее», а точнее – попытаемся разобраться позже...

¨ О средствах приведения-преобразования типов (Typecasts).

Строго типовые языки, к которым относится классический Pascal, «не любят» неявное приведение типов, но по необходимости «признают» явные преобразования типов. С появлением понятия «наследование» этот вопрос обостряется – объект типа класс-предок и объект типа класс-потомок имеют разные типы...

В Object Pascal 2 средства явного преобразования типов заметно расширены.

Новая конструкция языка: ИмяТипа(Выражение)

§ относится к категории «Переменные типа ИмяТипа», если «Выражение» является переменной,

§ но относится к категории «Выражения типа ИмяТипа», если «Выражение» не является переменной.

При этом естественно семантика предписывает соответствующее приведение значения Переменной-Выражения к типу ИмяТипа.

Например.

TYPE ShortInt=–128..127;

VAR MyChar: Char; MyInt: Integer;...

MyInt:= Integer('z');

ShortInt(MyChar):= 122;

MyInt получит значение 122 (это ASCII-код символа 'z'), а MyChar получит значение 'z'.

В этом примере преобразователь Integer() дает такое же значение как ORD(), а ShortInt(MyChar) не создает новую переменную типа ShortInt, а «как бы показывает MyChar в виде как бы ShortInt». Использовать просто Integer вместо ShortInt не удается, т.к. транслятор констатирует неприводимость Integer -> Char: у Integer область возможных значений больше, чем у Char, поэтому «невозможно на Char посмотреть с точки зрения Integer» (хотя конкретное значение 122 – допустимое).

¨ Спецификация методов класса – статические и виртуальные методы.

«Наследование-Полиморфизм» пара вида «Сохранение-Изменение»(*). Консервативное (только сохраняющее) наследование не очень способствует изменениям в поведении потомков (реализации полиморфного поведения унаследованных методов). На основе понятий «статические» и «виртуальные» методы можно (в какой-то мере) согласовать разнонаправленность целей.

§ Статические методы. По умолчанию методы статические. Переопределение статического метода подавляет унаследованный от родителя одноименный метод.

Переопределение с подавлением (скрытием, hiding) - это означает, что по типу объекта устанавливается, как будет выполняться вызванный (для объекта) метод. Если в классе–типе объекта этот метод переопределен, то действует его переопределенная реализация (из раздела implementation), если нет - то поднимаемся вверх по иерархии наследования к предку, в котором этот метод переопределен (или впервые определен).

ПРИМЕР (**).

Type

TFigure = class

procedure Draw;

end;

TRectangle = class (TFigure)

procedure Draw;

end;

Var

Figure: TFigure;

Rectangle: TRectangle;

Begin

Figure:= TFigure.Create;

Figure.Draw; // вызывается TFigure.Draw

Figure.Destroy;

Figure:= TRectangle.Create;

Figure.Draw; // вызывается TFigure.Draw

TRectangle(Figure).Draw; // вызывается TRectangle.Draw

Figure.Destroy;

Rectangle:= TRectangle.Create;

Rectangle.Draw; // вызывается TRectangle.Draw

Rectangle.Destroy;

end;

К моменту второго вызова Figure.Draw переменная Figure (вроде бы) получила значение типа TRectangle (см. Совместимость типов по присваиванию). Но ее тип не изменился, поэтому действует реализация TFigure.Draw.

В третьем вызове использовано преобразование типа TRectangle(Figure), которое конечно не изменяет тип переменной Figure, но теперь метод Draw вызывается уже для объекта типа TRectangle.

§ Виртуальные методы. Виртуальные методы специфицируются при объявлении метода в определении класса ключевым словом VIRTUAL (через; в конце заголовка).

Такие методы допускают переопределение двух видов:

· обычное (по умолчанию) - с подавлением унаследованного от родителя одноименного метода, ровно так же как и в случае статических методов;

· с перекрытием - это специфицируется при объявлении метода в определении класса-потомка ключевым словом OVERRIDE (через; в конце заголовка).

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

В случае переопределения с перекрытием метод класса-потомка должен иметь заголовок совместимый с заголовком одноименного метода класса-родителя – по количеству параметров, по их порядку и их типу.

ПРИМЕР (пример взят оттуда же, откуда предыдущий).

Type

TFigure = class

procedure Draw; virtual;

end;

TRectangle = class (TFigure)

procedure Draw; override;

end;

TEllipse = class (TFigure)

procedure Draw; override;

end;

TCircle = class (TFigure)

procedure Draw;

end;

Var

Figure: TFigure; Circle: TCircle;

Begin

Figure:= TRectangle.Create;

Figure.Draw; // вызывается TRectangle.Draw

Figure.Destroy;

Figure:= TEllipse.Create;

Figure.Draw; // вызывается TEllipse.Draw

Figure.Destroy;

Figure:= TCircle.Create;

Figure.Draw; // вызывается TFigure.Draw

Figure.Destroy;

Circle:= TCircle.Create;

Circle.Draw; // вызывается TCircle.Draw

Circle.Destroy;

end;

Хорошо видны явные отличия в поведении одного и того же метода Draw, вызываемого для одной и той же переменной Figure типа класс, в зависимости от ее значения, если имеет место переопределение с перекрытием. Столь же хорошо видны отличия от совершенно аналогичной ситуации в предыдущем примере. … И все же… если объект=переменная, то опять же вспомним, что переменная – не ее имя, и не ее значение, а пара… и даже больше, если пожелаем разобраться до конца…

¨ Как бы ни был переопределен метод в классе-потомке, остается возможность вызвать версию метода класса-родителя.

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

Если метод вызывается для объекта – в виде ИмяОбъекта.ИмяМетода …, то можно воспользоваться преобразователем типа – ИмяКлассаРодителя(ИмяОбъекта).ИмяМетода

Замечание. Семантика наследования и полиморфизма совсем не проста и многое здесь еще не устоялось так, как хотелось бы… Еще труднее «все увязать» в рамках строго типового языка... без сползания к безграничному релятивизму и беспринципному практическому прагматизму... «так это реализовано – запрограммировано кем-то где-то в каком-то случае...»(*).

ПЕРЕГРУЗКА (Overloading) ПРОЦЕДУР, ФУНКЦИЙ И МЕТОДОВ.

Это средство программирования напрямую не связано с понятием класс, но имеет прямое отношение к проблемам описания полиморфного поведения процедур, функций и методов.

Простой пример достаточно хорошо поясняет смысл этого средства:

PROGRAM pp; {$APPTYPE CONSOLE}

FUNCTION Sum(x,y: REAL):REAL; OVERLOAD; BEGIN Sum:=x+y END;

FUNCTION Sum(x,y: STRING):STRING; OVERLOAD; BEGIN Sum:=x+y END;

FUNCTION Dvs(x,y:REAL):REAL; OVERLOAD; BEGIN Dvs:=x/y END;

FUNCTION Dvs(x,y:INTEGER):INTEGER; OVERLOAD; BEGIN Dvs:=x DIV y END;

BEGIN

WRITELN(5/2);

WRITELN(Dvs(5,2)); WRITELN(Dvs(5.0,2.0));

WRITELN(Dvs(5.0,2)); WRITELN(Dvs(5,2.0));

WRITELN(Sum(2.3,7.5)); WRITELN(Sum('2.3','7.5'));

READLN;

END.

Как показывает пример, можно описать несколько одноименных функций (или процедур) – с одинаковым числом параметров, но разного типа. Все эти описания должны иметь спецификатор OVERLOAD. Для каждого вызова этих функций выполняется одна из ее реализаций, соответствующая списку типов фактических параметров.

ОСНОВНОЙ ПРИМЕР - наследование и полиморфизм. Точки, отрезки, треугольники. PROGRAM\OOP\PRG01\PRIMAG.DPR


(*) Сегодня, на новом витке мы опять вернулись к довольно опасному состоянию языков практического программирования:

· классический Pascal основательно защищал программиста от опасных ошибок;

· современный Pascal основательно снижает трудоемкость написания больших программ, но гораздо слабее защищает программиста от опасных ошибок (своих и «унаследованных»);

· положение в других языках практического программирования (Си ++, Basic,…) – не лучше.

Надежность программного обеспечения (ПО) сегодня в основном удерживается не благодаря языку, на котором программируют, а благодаря технологии программирования – от этапа проектирования до разработки и написания программ. Серьезная фирма, разработчик ПО, имеет внутрифирменные стандарты технологии программирования, и внимательно «бдит» за их неукоснительным соблюдением. Соблюдение правил оказывается даже важнее вопроса «что хорошо, а что плохо», поскольку проблемы контроля надежности ПО перекрывают все другие «хорошо».

(**) В развитой форме определение абстрактного типа данных включает 4 части:

· внешность (видимая часть, интерфейс,…) – имя определяемого типа (понятия), имена операций с указанием типов их аргументов,…

· абстрактное описание операций и объектов, с которыми они работают, средствами некоторого языка спецификаций;

· конкретное описание (реализация, представление,…) тех же операций средствами «обычного» языка программирования (<= 2-го поколения);

· описание связи между «абстрактным» и «конкретным», объясняющее, в каком смысле «конкретное» представляет – реализует «абстрактное».

Инкапсуляция (encapsulation) – «скрытие» конкретного описания (защита от внешнего воздействия), из вне модуля доступны данные и операции, определенные в модуле (локальные???!!!), но только те, которые объявлены во внешности (как видимые). Понятие инкапсуляция вносит существенно новое в дилемму локальные - глобальные объекты.

(*) К сожалению, всегда находятся люди, имеющие неодолимое желание «с помощью своего гвоздика помочь часикам лучше работать». Обычно, их вмешательство имеет катастрофические последствия для «часиков», но нередко – и для этих и для других людей.

(*) Не представляется возможным рациональное использование инструментов, если в нормальной голове не укладывается даже назначение всех этих «отверточек», «молоточков» и прочих штучек, даже количество и название которых вспоминается с трудом…

(*) И еще раз хочется акцентировать внимание на принципиально важном для правильного понимания всего этого. Если я не отличаю (или не хочу отличать) «Камского леща» от «Волжского» и других, то это нормально – в моей классификации у класса «лещ» не будет подклассов. Если в некоторой предметной области не различать понятия «человек» и «человечество», то это даже хорошо, если постановка и решение задач в этой предметной области не зависит от различий между этими понятиями.

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

(*) В итоге мы получили вполне приемлемую реализацию абстрактного типа данных «Линейный однонаправленный список», которую, кстати, можно еще и улучшить. Правда, надо отметить, что ориентированный на объекты стиль этой реализации все же заметно отличается от классического АТД-стиля.

(*) Знания преумножаются, когда сохраняются унаследованные сведения о фактах и методах их использования, но и изменяются – переосмысливаются и факты и методы их использования.

(**) взят их Help > Object Pascal Reference (Object Pascal Language Guide) > Classes and objects…

 

(*) Тем не менее, операциональное описание семантики средств языка на основе подходящей модели базового вычислителя всегда представляет самостоятельный интерес…

Если все же попытаться разобраться в смысле различия между статическими и виртуальными методами через реализацию…

Уже традиционные переменные различаются по способу связывания с ними памяти для хранения их значений. Статические переменные «имеют память» (создаются) изначально - это переменные головной программы (описанные в program, нелокализованные в ее процедурах и функциях). Динамические переменные создаются (связываются с памятью) в периоде выполнения программы оператором NEW. Переменные, описанные в процедурах и функциях (локализованные в них), «живут» только в периоде выполнения своей процедуры-функции (такие переменные обычно называют автоматическими).

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

Методы различаются по способу связывания (Method binding) с объектами:

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

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

В Object Pascal 2 имеются еще динамические методы. Семантически они неотличимы от виртуальных, но реализация позднего связывания несколько отличается. Считается, что при использовании динамических методов вместо виртуальных экономится память, но программа будет работать медленнее.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...