лекции (2008) (by Михайлишин Алексей_ Жбанков Денис_ Щербинин Виктор_ Чеботарев Павел) (1160831), страница 13
Текст из файла (страница 13)
};Идеология наследования такова, что когда мы проектируем структуру классов, мы заранее знаем, что и откого будет наследоваться, и пишем классы именно такими. Хотя сперва, кажется, что писать виртуальноенаследование класса, не зная, кто, будет наследовать его нельзя. Если перед классом написано final (вJava), то он «финальный», «запечатанный» и от него нельзя наследоваться. Такие классы имеют большуюпроизводительность.
Есть еще причины использования их вместо наследуемых.Глава 2. Динамическое связывание методовИспользование субкласса вместо суперкласса не должно вызывать потери надежности. Ничего не должноменяться при передаче указателя на субкласс в параметрах вместо указателя на суперкласс. Но всеменяется, когда мы передаем параметры по ссылке.В С# мы можем писать так:var x = new System.Windows.Forms.Form(...);По идее, компилятор всегда сам может определить тип выражения.ссылки всегда меньше или равен статическому типу.
Объект данныхпамяти. Если уж объект base размещен в памяти, то он не может вдругкуска памяти мы уже не изменим. А вот ссылки или указатели – можем.вполне определенный тип.Динамический тип указатели или– это инициализированный кусокрасшириться до derived. Тип этогоОни в свою очередь указывают наНестатический метод статически привязан. Он всегда вызывается через ссылку. Его вызов осуществляетсяисходя из статической ссылки. Динамический метод – из динамической. Ковариантность – если тип X < Y,он может совпадать с Y или быть наследованным (или приводимым) от него.class Y {public void f() {...}}Class X extends Y {public int f() {...}}В языке Java все методы связаны динамически.
В C#, Delphi – существует специальный модификаторvirtual, то метод становится динамически связанным. Это свойство вызова, но мы привязываем его кметоду. Пример://наследование такое: A => B => C => D => ...B *pb;pb->f(); //компилятор ищет функцию в B с таким профилем, потом идет в объемлющийуровень, пока не найдет нужный методvoid B::g() { //в этом случае применяются те же самые правилаf():}Если f – статический метод, то ищется функция до загрузки программы в память. Если же динамическийметод - то в момент выполнения, ищем ковариантные переопределения (именно в момент выполнения).Если переопределена приватная функция, то она вызвана не будет. Компилятор Java даже не видитприватные функции, C++ видит, но предупреждает, что доступа нет.class A {private:virtual void f() {...}};class B: public A {private:virtual void f() {...} //переопределили, но не можем сделать ее public}Здесь мы даем базовому классу вызывать нашу приватную функцию.
Другие классы по-прежнему не могутвызывать эту функцию.В ряде языков (в том числе С++) есть возможность снятия механизма виртуальности вызова. Например,когда нам не надо рекурсивно зацикливаться. Снятие происходит путем уточнения имени класса.Void PatternRect::Draw(){Fill(pattern, ...);Rect::Draw();//сняли виртуальности}В отличие от override, overload означает, что надо либо целиком замещать методы, либо использовать ихбез изменения.Динамическое связывание методов.С динамическим связыванием методов связаны понятия виртуализации методов, замещения ипереопределения.C++. В этом ЯП метод является виртуальным, если он помечен virtual.class Base{virtual void f();}Метод в производном классе является замещающим, если у него совпадает имя и сигнатура (в.т.ч.
все типыпараметров) и возвращаемое значение ковариантно (напр., если в базовом классе возвращаемое значение– указатель на класс Т, то в замещающей функции тип возвращяемого значения может быть указателем наТ или на производный класс от Т).«Виртуальные конструкторы» в С++.class Base{public:virtual Base *clone();}class Derived: public Base{public:Derived *clone(); /* здесь virtual уже ставить не обязательно – метод всёравно будет виртуальным. Хотя для удобочитаемости кода virtual в таких случаяхлучше писать */}Механизм виртуальных методов открывает «дыру» в инкапсуляции:class X{public: virtual void f()}class Y: public X{private: virtual void f()}X *px = new Y;px->f()// – вызовется приватный метод класса Y.Java. Все методы виртуальные, правила замещения такие же, как в С++.class X{public void f()}class Y extends X{private void f()}X xx = new Y();xx.f() – будет вызвана функция из класса Х, т.к.
в Y мы сделали ее приватной (в отличии от аналогичногопримера на С++ - там была бы вызвана функция из класса Y). Это происходит потому, что в Java public,protected, private – модификаторы доступа, а не видимости.Если перед методом стоит модификатор final, то этот метод замещать нельзя.Если в производном классе имеется метод с тем же именем, что и в базовом класса, то имеет местоскрытые, а не замещение.
Если же сигнатура совпадает , но возвращаемый тип не ковариантен, то этоошибка времени компиляции.class Y extends X{public final void f();}Y yy = new Yderived();// Yderived – некоторый производный от Y классy.f() // всегда будет вызвана функция из YC#, Delphi.В этих языках есть как виртуальные, так и невиртуальные методы (как в С++).
Но функция,удовлетворяющая трем требованиям, не обязательно будет виртуальной. Для замещения нужно добавлятьoverride.Пример на C#.class X{public void f()}class Y: X{private override void f()}X x = new Y();x. f();Если нет override, то вызовется функция из класса X, иначе – из Y. При этом, если нет override, то в любомпроизводном классе от Y можно будет замещать только функцию f() из Y. Если замещение не нужно, торекомендуется ставить перед объявлением ключевое слово new (т.е.
указать, что поведение меняется).Пример на Delphitype X = classprocedure P; virtual;end;type X = class(X)procedure P; {без слова override эта процедура не будет замещать P из X}end;Ключевое слово virtual в этих языках означает начало цепочки динамической связи, а override –продолжение этой цепочки.Пример на C#.class X{public void f()}class Y: X{private void f()}class Z: Y{private override void f()}X z = new Z();z.f(); // вызовется метод из XОберон. Динамическое привязывание к типу.TYPE FIGURE = RECORDEND;TYPE LINE = RECORD(FIGURE)...END;TYPE CIRCLE = RECORD(FIGURE)...END;PROCEDURE DRAWLINE(VAR X:LINE)...PROCEDURE DRAWCIRCLE(VAR X:LINE)...PROCEDURE DRAW(VAR X:FIGURE)...Для вызова нужной процедуры в первой версии Оберона нужно было использовать диспетчеризацию потипу.
Тело процедуры DRAW:IF X IS LINE THENDRAWLINE(LINE.X);ELSEIF X IS CIRCLE THENDRAWCIRCLE(CIRCLE.X)...Это напоминает механизм обработки записей с вариантами, такой код – не объектно-ориентированный.Оберон – 2.PROCEDURE (VAR F:FIGURE) DRAW*();PROCEDURE (VAR C:CIRCLE) DRAW*();PROCEDURE (VAR L:LINE) DRAW*();Это единственный случай в Обероне-2, когда возможно использование одного имени в разных смыслах водной области видимости.Вызов:F.DRAW();В псевдомодуле документации появится следующее:TYPE FIGURE = RECORDPROCEDURE DRAW();END;Таким образом, синтаксис в этом случае похож на синтаксис ООП.Как снять механизм динамической связи в этом случае?(В Дельфи inherited P; В Java – Super.f();)DRAW^(); - вызов унаследованной реализации.Ада 95.В тех языках, которые мы уже рассмотрели в связи с динамическим связыванием, виртуальность быласвойством функции, и она была привязана к конкретному типу.Мультиметод – метод, динамически привязанный к типу не одного объекта, а нескольких.
Гипотетическийпример:Figure Intersect(f1, f2: Figure);Для каждой пары типов мы должны написать свою реализацию этой функции. В языках, которые мы покарассматривали, мультиметодов нет. Аду95 легко расширить так, чтобы она поддерживала мультиметоды(однако, она их не поддерживает).type Base is taged record ... end record;type Derived is new Base with record ...новые члены...
end record;Все переменные типа Derived являются переменными типа Base, правила совместимости типов ираспределения памяти – такие же, как в С++.procedure P(x:Base);procedure P(y:derived); - пока нет никакого замещенияb:Base;d:Derived;P(b); - вызывается первая PP(d); - вызывается вторая Pprocedure P(y:derived) isbegin...P(y’Base); - указание рассматривать y как объект типа Base.end P;В Аде95 введены новые понятия: cw – переменные и cw – типы (классовые типы). CW-wide.T taged record ...T’class – классовый тип, являющийся объединением типа Т и производных от него.X:Base’class; - этой переменной можно присваивать значения типа Base и производных от Base.P(X); - динамический вызов (в зависимости от того, какой тип у X, будет вызвана соотв.
функция).Переменные классового типа кроме ссылки на объект содержат информацию о типах.Figure → Line → CircleDrawLine …DrawCircle …DrawAll(F: Figure’class); - сама эта процедура статически привязана, ноprocedure DrawAll(F: Figure’class) isbeginDraw(P); - динамический вызовend;Использование:L:Line;C:Circle;DrawAll(L);DrawAll(C);Но такую вещь делать нельзя, т.к.