Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Другие элементы меню 15 страница




q у структуры нет конструкторов;

q у структуры нет наследников;

q сложение двух точек – структур Point создает линию – структуру Line;

q сложение точки – структуры Point с размером – структурой Size дает новую точку.

48. При создании объектов перечислений:

q нельзя вызывать конструктор;

q требуется явная инициализация;

q объекты получают значение, даже если инициализация не задана;

q значение объекта всегда принадлежит множеству, заданному перечислением.

Вариант 2

50. Особенностями структур являются:

q структуры не могут наследовать интерфейсы;

q структуры не имеют наследников;

q поля структуры не могут быть инициализированы при ее объявлении;

q для структур нельзя создать собственный конструктор без аргументов.

51. Отметьте истинные высказывания:

q объекты развернутого типа не разделяют память;

q все значимые типы языка C# реализованы структурами;

q конструктору встроенной структуры Rectangle можно передать объекты Point и Size;

q при присваивании значения структуре изменяется ссылка, указывающая теперь на новый объект.

52. Укажите объявления, приводящие к ошибке:

q public static enum ThreeItems{one, two, three};

q public enum Three {раз, два, три};

q new public enum ThreeItems{zero, one, two};

q private enum Chars{‘a’, ‘b’, ‘c’ ‘d’};

q private enum Alpha{a, b, c, d}.

Вариант 3

47. Какие утверждения справедливы относительно взаимных преобразований структур в классы?

q для преобразования структуры в класс достаточно заменить слово struct словом class;

q для преобразования класса в структуру достаточно заменить слово class словом struct;

q для преобразования класса в структуру иногда достаточно заменить слово class словом struct;

q для преобразования класса в структуру необходимо изменить конструктор класса;

q для преобразования класса в структуру иногда необходимо изменить конструктор класса

48. Отметьте истинные высказывания:

q поля структуры могут быть ссылками;

q перечисление относится к значимому (развернутому) типу;

q структура наследует методы класса object;

q перечисление наследует методы класса object;

q структуры и перечисления – частные случаи классов.

49. Даны объявления структур:

Point pt1 = new Point(3,5),pt2; Size sz1 = new Size(7,10), sz2; Rectangle rect;

Какие операторы присваивания ошибочны?

q pt2 = pt1 +sz1 +sz2;

q sz2 = pt2;

q pt2 +=pt1;

q pt2 +=sz2;

q pt2 = pt1 +1;

q sz2 = new Size(pt1);

 

 

Лекция 18 Отношения между классами. Клиенты и наследники

Классы. Отношения между классами. Отношение клиенты – поставщики. Отношение наследования. Единичное наследование. Родители и наследники. Предки и потомки. Что наследуют потомки. Что могут изменить потомки. Одностороннее присваивание. Контроль типов и связывание – статическое и динамическое. Полиморфизм. Проектирование классов. Абстрактные классы. Классы поведения.

Отношения между классами

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

Определение 1 Классы А и В находятся в отношении «клиент – поставщик», если одним из полей класса В является объект класса А. Класс А называется поставщиком класса В, класс В называется клиентом класса А.

Определение 2 Классы А и В находятся в отношении «родитель – наследник», если при объявлении класса В класс А указан в качестве родительского класса. Класс А называется родителем класса В, класс В называется наследником класса А.

Оба отношения наследования и вложенности являются транзитивными. Если В клиент А и С клиент В, то отсюда следует, что С клиент А. Если В наследник А и С наследник В, то отсюда следует, что С наследник А.

Определения 1 и 2 задают прямых или непосредственных клиентов и поставщиков, прямых родителей и наследников. Вследствие транзитивности необходимо ввести понятие уровня. Прямые клиенты и поставщики, прямые родители и наследники относятся к соответствующему уровню 1 (клиенты уровня 1, поставщики уровня 1и так далее). Затем следует рекурсивное определение: прямой клиент клиента уровня k относится к уровню k+1.

Для отношения наследования используется терминология, заимствованная из естественного языка. Прямые классы-наследники часто называются сыновними или дочерними классами. Непрямые родители называются предками, а их непрямые наследники – потомками.

Замечу, что цепочки вложенности и наследования могут быть достаточно длинными. На практике вполне могут встречаться цепочки длины 10. Например библиотечные классы, составляющие систему Microsoft Office, полностью построены на отношении вложенности. При программной работе с объектами Word можно начать с объекта, задающего приложение Word и добраться до объекта, задающего отдельный символ в некотором слове некоторого предложения одного из открытых документов Word. Для выбора нужного объекта можно задать такую цепочку: приложение Word – коллекция документов – документ – область документа – коллекция абзацев – абзац – коллекция предложений – предложение – коллекция слов – слово – коллекция символов – символ. В этой цепочке каждому понятию соответствует класс библиотеки Microsoft Office, где каждая пара соседствующих классов связана отношением поставщик – клиент.

Классы библиотеки FCL связаны как отношением вложенности, так и отношением наследования. Длинные цепочки наследования достаточно характерны для классов этой библиотеки.

Отношения «является» и «имеет»

При проектировании классов часто возникает вопрос, какое же отношение между классами нужно построить. Рассмотрим совсем простой пример двух классов – Square и Rectangle, описывающих квадраты и прямоугольники. Наверное понятно, что скорее всего эти классы следует связать отношением наследования, чем вложенности, менее понятным остается вопрос, а какой из этих двух классов следует сделать родительским. Еще один пример двух классов Car и Person, описывающих автомобиль и персону. Какими отношениями с этими классами должен быть связан класс Person_of_Car, описывающий владельца машины? Может ли он быть наследником обоих классов? Найти правильные ответы на эти вопросы проектирования классов помогает понимание того, что отношение «клиент – поставщик» задает отношение «имеет» (“has”), а отношение наследования задает отношение «является» (“is a”). В случае классов Square и Rectangle понятно, что каждый объект квадрат «является» прямоугольником, поэтому между этими классами имеет место отношение наследования и родительским классом является класс Rectangle, а класс Square является его потомком.

В случае автомобилей, персон и владельцев авто также понятно, что владелец «имеет» автомобиль и «является» персоной. Поэтому класс Person_of_Car является клиентом класса Car и наследником класса Person.

Отношение вложенности

Рассмотрим два класса A и B, связанных отношением вложенности. Оба класса применяются для демонстрации идей и потому устроены просто, не неся особой смысловой нагрузки. Пусть класс поставщик A уже построен. У класса два поля, конструктор, один статический и один динамический метод. Вот его текст:

public class ClassA

{

public ClassA(string f1, int f2)

{

fieldA1 = f1; fieldA2 = f2;

}

public string fieldA1;

public int fieldA2;

public void MethodA()

{

Console.WriteLine("Это класс A");

Console.WriteLine ("поле1 = {0}, поле2 = {1}",

fieldA1, fieldA2);

}

public static void StatMethodA()

{

string s1 = "Статический метод класса А";

string s2 = "Помните: 2*2 = 4";

Console.WriteLine(s1 + " ***** " + s2);

}

}

Построим теперь класс B – клиента класса A. Класс будет устроен похожим образом, но в дополнение будет иметь одним из своих полей объект inner класса A:

public class ClassB

{

public ClassB(string f1A, int f2A, string f1B, int f2B)

{

inner = new ClassA(f1A, f2A);

fieldB1 = f1B; fieldB2 = f2B;

}

ClassA inner;

public string fieldB1;

public int fieldB2;

public void MethodB1()

{

inner.MethodA();

Console.WriteLine("Это класс B");

Console.WriteLine ("поле1 = {0}, поле2 = {1}",

fieldB1, fieldB2);

}

Обратите внимание, конструктор клиента (класса B) отвечает за инициализацию полей класса, поэтому он должен создать объект поставщика (класса A), вызывая, как правило, конструктор поставщика. Если для создания объектов поставщика требуются аргументы, то они должны передаваться конструктору клиента, как это сделано в нашем примере.

После того как конструктор создал поле – объект поставщика, методы класса могут использовать этот объект, вызывая доступные клиенту методы и поля класса поставщика. Метод класса B – MethodB1 начинает свою работу с вызова: inner.MethodA, используя сервис, поставляемый методом класса A.

Расширение определения клиента класса

До сих пор мы говорили, что клиент содержит поле, представляющее объект класса поставщика. Это частая, но не единственная ситуация, когда класс является клиентом другого класса. Возможна ситуация, когда метод клиентского класса локально создает объект поставщика, вызывает его методы в собственных целях, но по завершении метода локальный объект заканчивает свою жизнь. Еще одна возможная ситуация когда объекты поставщика вообще не создаются ни конструктором, ни методами класса клиента, но клиент вызывает статические методы класса поставщика. Оба эти варианта демонстрируют следующие два метода класса B:

public void MethodB2()

{

ClassA loc = new ClassA("локальный объект А",77);

loc.MethodA();

}

public void MethodB3()

{

ClassA.StatMethodA();

}

Дадим теперь расширенное определение клиента:

Определение 3: Класс B называется клиентом класса A, если в классе B создаются объекты класса A – поля или локальные переменные – или вызываются статические поля или методы класса A.

Отношения между клиентами и поставщиками

Что могут делать клиенты и что могут делать поставщики? Класс поставщик создает свойства (поля) и сервисы (методы), предоставляемые своим клиентам. Клиенты создают объекты поставщика. Вызывая доступные им методы и поля объектов, они управляют работой созданных объектов поставщика. Клиенты не могут ни изменить поведение методов поставщика, ни изменить состав предоставляемых им полей и методов, они не могут вызывать закрытые поставщиком поля и методы класса.

Класс поставщик интересен клиентам своей открытой частью, составляющей интерфейс класса. Но большая часть класса может быть закрыта для клиентов, им незачем вникать в детали представления и в детали реализации. Скрытие информации вовсе не означает, что разработчики класса не могут быть знакомы с тем, как все реализовано, хотя иногда и такая цель преследуется. В общем случае скрытие означает, что классы клиенты строят свою реализацию, основываясь только на интерфейсной части класса поставщика. Поставщик закрывает поля и часть методов класса от клиентов, задавая для них атрибут доступа private или protected. Он может некоторые классы считать привилегированными, предоставляя им методы и поля, недоступные другим классам. В этом случае поля и методы, предназначенные для таких vip-персон, снабжаются атрибутом доступа internal, а классы с привилегиями должны принадлежать одной сборке.

В заключение построим тест, проверяющий работу с объектами классов A и B:

public void TestClientSupplier()

{

ClassB objB = new ClassB("AA",22, "BB",33);

objB.MethodB1();

objB.MethodB2();

objB.MethodB3();

}

Результаты работы этого теста показаны на рис. 18.1

Рис. 18.1 Клиенты и поставщики

Сам себе клиент

Зададимся вопросом, может ли класс быть сам себе клиентом, другими словами, может ли поле класса быть объектом описываемого класса? Другой не менее интересный вопрос, могут ли два класса быть одновременно клиентами и поставщиками друг для друга? Ответы на оба вопросы положительны и подобные ситуации типичны и не являются какой-либо экзотикой.

Первая ситуация характерна для динамических структур данных. Элемент односвязного списка имеет поле, представляющее элемент односвязного списка, элемент двусвязного списка имеет два таких поля, узел двоичного дерева имеет два поля, представляющих узлы двоичного дерева. Эта ситуация характерна не только для рекурсивно определяемых структур данных. Вот еще один типичный пример. В классе Person могут быть заданы два поля – Father и Mother, задающие родителей персоны, и массив Children. Понятно, что все эти объекты могут быть того же класса Person.

Не менее частая ситуация, когда классы имеют поля, взаимно ссылающиеся друг на друга. Типичным примером могут служить классы Man и Woman, первый из которых имеет поле wife класса Woman, а второй – поле husband класса Man.

Заметьте, классы устроены довольно просто, их тексты понятны, отношения между классами очевидны. А вот динамический мир объектов этих классов может быть довольно сложным, отношения между объектами могут быть запутанными, для объектов характерны не только любовные треугольники, но и куда более сложные фигуры.

Наследование

Мощь ООП основана на наследовании. Когда построен полезный класс, то он может многократно использоваться. Повторное использование – это одна из главных целей ООП. Но и для хороших классов неизбежно наступает момент, когда необходимо расширить возможности класса, придать ему новую функциональность, изменить интерфейс. Всякая попытка изменять сам работающий класс чревата большими неприятностями – могут перестать работать прекрасно работавшие программы, многим клиентам класса вовсе не нужен новый интерфейс и новые возможности. Здесь то и приходит на выручку наследование. Существующий класс не меняется, но создается его потомок, продолжающий дело отца, но на новом уровне.

Класс потомок, наследует все возможности родительского класса – все поля и все методы, открытую и закрытую часть класса. Правда не ко всем полям и методам класса возможен прямой доступ потомка. Поля и методы родительского класса, снабженные атрибутом private, хотя и наследуются, но по-прежнему остаются закрытыми и методы, создаваемые потомком, не могут к ним обращаться напрямую, а только через методы, наследованные от родителя. Единственное, что не наследует потомок – это конструкторы родительского класса. Конструкторы потомок должен создавать сам. В этом есть некоторая разумная идея, и я позже поясню ее суть.

Рассмотрим класс, названный Found, играющий роль родительского класса, у него есть обычные поля, конструкторы и методы, один из которых снабжен новым модификатором virtual, ранее не появлявшимся в классах, другой – модификатором override:

public class Found

{

//поля

protected string name;

protected int credit;

public Found()

{

}

public Found(string name, int sum)

{

this.name = name; credit = sum;

}

public virtual void VirtMethod()

{

Console.WriteLine ("Отец: " + this.ToString());

}

public override string ToString()

{

return(String.Format("поля: name = {0}, credit = {1}",

name, credit));

}

public void NonVirtMethod()

{

Console.WriteLine ("Мать: " + this.ToString());

}

public void Analysis()

{

Console.WriteLine ("Простой анализ");

}

public void Work()

{

VirtMethod();

NonVirtMethod();

Analysis();

}

}

Заметьте, класс Found, как и все классы, по умолчанию является наследником класса object, его потомки наследуют методы этого класса уже не напрямую, а через методы родителя, который мог переопределить методы класса object. В частности, класс Found переопределил метод ToString, задав собственную реализацию возвращаемой методом строки, связываемой с объектами класса. Как это часто делается, в этой строке отображаются значения полей объекта данного класса. На переопределение родительского метода ToString указывает модификатор метода override.

Класс Found закрыл свои поля для клиентов, но открыл их для потомков, снабдив их модификатором доступа protected.

Создадим теперь класс Derived – потомка класса Found. В простейшем случае объявление класса может выглядеть так:

public class Derived:Found

{

}

Тело класса Derived пусто, но это вовсе не значит, что объекты этого класса не имеют полей и методов, они «являются» объектами класса Found, наследуя все его поля и методы (кроме конструктора) и поэтому могут делать все, что могут делать объекты родительского класса.

Вот пример работы с объектами родительского и производного класса:

public void TestFoundDerived()

{

Found bs = new Found ("father", 777);

Console.WriteLine("Объект bs вызывает методы базового класса");

bs.VirtMethod();

bs.NonVirtMethod();

bs.Analysis();

bs.Work();

Derived der = new Derived();

Console.WriteLine("Объект der вызывает методы класса потомка");

der.VirtMethod();

der.NonVirtMethod();

der.Analysis();

der.Work();

}

Результаты работы этой процедуры показаны на рис. 18.1.

Рис. 18.1 Объект потомка наследует поля и методы родителя

В чем отличие работы объектов bs и der? Поскольку класс-потомок Derived ничего самостоятельно не определял, то он наследовал все поля и методы у своего родителя, за исключением конструкторов. У этого класса имеется собственный конструктор без аргументов, задаваемый по умолчанию. При создании объекта der вызывался его собственный конструктор по умолчанию, инициализирующий поля класса значениями по умолчанию. Об особенностях работы конструкторов потомков скажу чуть позже, сейчас же скажу лишь, что конструктор по умолчанию потомка вызывает конструктор без аргументов своего родителя, поэтому для успеха работы родитель должен иметь такой конструктор. Заметьте, поскольку родитель не знает, какие у него могут быть потомки, то желательно конструктор без аргументов включать в число конструкторов класса, как это сделано для класса Found.

Добавление полей потомком

Ничего не делающий самостоятельно потомок не эффективен, от него мало проку. Что же может делать потомок? Прежде всего, он может добавить новые свойства – поля класса. Заметьте, потомок не может ни отменить, ни изменить модификаторы или типы полей, наследованных от родителя, он может только добавить собственные поля.

Модифицируем наш класс Derived. Пусть он добавляет новое поле класса, закрытое для клиентов этого класса, но открытое для его потомков:

protected int debet;

Напомню, хорошей стратегией является стратегия «ничего не скрывать от потомков». Какой родитель знает, что, из того, что он сделал, может понадобиться потомкам?

Конструкторы родителей и потомков

Каждый класс должен позаботиться о создании собственных конструкторов. Он не может в этом вопросе полагаться на родителя, поскольку, как правило, добавляет собственные поля, о которых родитель ничего не может знать. Конечно, если не задать конструкторов класса, то будет добавлен конструктор по умолчанию, инициализирующий все поля значениями по умолчанию, как это мы видели в предыдущем примере. Но это редкая ситуация. Чаще всего, класс создает собственные конструкторы и, как правило, не один, задавая разные варианты инициализации полей.

При создании конструкторов классов потомков есть одна важная особенность. Всякий конструктор создает объект класса – структуру, содержащую поля класса. Но потомок, прежде чем создать собственный объект, вызывает конструктор родителя, создавая родительский объект, который затем будет дополнен полями потомка. Ввиду транзитивности этого процесса, конструктор родителя вызывает конструктор своего родителя, этот процесс продолжается пока первым делом не будет создан объект прародителя.

Вызов конструктора родителя происходит не в теле конструктора, а в заголовке конструктора, пока еще не создан объект класса. Для вызова конструктора используется ключевое слово base, именующее родительский класс. Как это делается, покажу на примере конструкторов класса Derived:

public Derived() {}

public Derived(string name, int cred, int deb): base (name,cred)

{

debet = deb;

}

Для конструктора без аргументов вызов аналогичного конструктора родителя подразумевается по умолчанию. Для конструкторов с аргументами вызов конструктора с аргументами родительского класса должен быть явным. Этот вызов синтаксически следует сразу за списком аргументов конструктора, будучи отделен от этого списка символом двоеточия. Конструктору потомка передаются все аргументы, необходимые для инициализации полей, часть из которых передаются конструктору родителя для инициализации родительских полей.

Итак, вызов конструктора потомка приводит к цепочке вызовов конструкторов предков, заканчивающейся вызовом конструктора прародителя. Затем в обратном порядке создаются объекты, начиная с объекта прародителя, выполняются тела соответствующих конструкторов, инициализирующие поля и выполняющие другую работу этих конструкторов. Последним создается объект потомка и выполняется тело конструктора потомка.

Добавление методов и изменение методов родителя

Потомок может создать новый собственный метод с именем, отличным от имен наследуемых методов. В этом случае никаких особенностей нет. Вот пример такого метода, создаваемого в классе Derived:

public void DerivedMethod()

{

Console.WriteLine("Это метод класса Derived");

}

В отличие от неизменяемых полей классов предков класс потомок может изменять наследуемые им методы. Если потомок создает метод с именем, совпадающим с именем метода предков, то возможны три ситуации:

Перегрузка метода. Она возникает, когда сигнатура создаваемого метода отличается от сигнатуры наследуемых методов предков. В этом случае в классе потомка будет несколько перегруженных методов с одним именем, и вызов нужного метода определяется обычными правилами перегрузки методов.

Переопределение метода. Метод родителя в этом случае должен иметь модификатор virtual или abstract. Эта наиболее интересная ситуация подробно будет рассмотрена в следующих разделах этой лекции. При переопределении сохраняется сигнатура и модификаторы доступа наследуемого метода.

Скрытие метода. Если родительский метод не является виртуальным или абстрактным, то потомок может создать новый метод с тем же именем и той же сигнатурой, скрыв родительский метод в данном контексте. При вызове метода предпочтение будет отдаваться методу потомка, а имя наследуемого метода будет скрыто. Это не означает, что оно становится недоступным. Скрытый родительский метод всегда может быть вызван, если при вызове уточнить ключевым словом base имя метода.

Метод потомка, скрывающий метод родителя, следует сопровождать модификатором new, указывающим на новый метод. Если этот модификатор опущен, но из контекста ясно, что речь идет о новом методе, то выдается предупреждающее сообщение при компиляции проекта.

Вернемся к нашему примеру. Класс Found имел в своем составе метод Analysis. Его потомок класс Derived создает свой собственный метод анализа, скрывая метод родителя:

new public void Analysis()

{

base. Analysis();

Console.WriteLine("Сложный анализ");

}

Если модификатор new опустить, он бы добавился по умолчанию с выдачей предупреждающего сообщения о скрытии метода родителя. Как компилятор узнает, что в этой ситуации речь идет о новом методе? Причины понятны. С одной стороны родительский метод не имеет модификаторов virtual или abstract, поэтому речь не идет о переопределении метода. С другой стороны в родительском классе уже есть метод с данным именем и сигнатурой, и поскольку в классе не могут существовать два метода с одинаковой сигнатурой, то речь может идти только о новом методе класса, скрывающем родительский метод. Несмотря на «интеллект» транслятора хороший стиль программирования требует явного указания модификатора new в подобных ситуациях.

Заметьте, потомок строит свой анализ на основе метода, наследованного от родителя, вызывая первым делом скрытый родительский метод.

Рассмотрим случай, когда потомок добавляет перегруженный метод. Вот пример, когда потомок класса Derived – класс ChildDerived создает свой метод анализа, изменяя сигнатуру метода Analysis:

public void Analysis(int level)

{

base. Analysis();

Console.WriteLine("Анализ глубины {0}", level);

}

Большой ошибки не будет, если указать модификатор new и в этом случае, но будет выдано предупреждающее сообщение, что модификатор может быть опущен, поскольку скрытия родительского метода не происходит.

Статический контроль типов и динамическое связывание

Рассмотрим семейство классов A1, A2, … An, связанных отношением наследования. Класс Ak+1 является прямым потомком класса Ak. Пусть создана последовательность объектов x1, x2, … xn, где xk – это объект класса Ak. Пусть в классе A1 создан метод M с модификатором virtual, переопределяемый всеми потомками, так что в рамках семейства классов метод M существует в n формах, каждая из которых задает реализацию метода, выбранную соответствующим потомком. Рассмотрим основную операцию, инициирующую объектные вычисления – вызов объектом метода класса:

x1.M(arg1, arg2, … argN)

Контролем типов называется проверка каждого вызова, удостоверяющая, что:

в классе A1 объекта x1 действительно имеется метод M;

список фактических аргументов в точке вызова соответствует по числу и типам списку формальных аргументов метода M, заданного в классе A1.

Язык C#, как и большинство других языков программирования, позволяет выполнить эту проверку еще на этапе компиляции и в случае нарушений выдать сообщение об ошибки периода компиляции. Контроль типов, выполняемый на этапе компиляции, называется статическим контролем типов. Некоторые языки, например Smalltalk, производят этот контроль динамически – непосредственно перед выполнением метода. Понятно, что ошибки, обнаруживаемые при динамическом контроле типов трудно исправимы и потому приводят к более тяжелым последствиям. В таких случаях остается уповать на то, что система тщательно отлажена, иначе непонятно, что будет делать конечный пользователь, получивший сообщение о том, что вызываемого метода вообще нет в классе данного объекта.

Перейдем к рассмотрению связывания. Напомним, что в рассматриваемом семействе классов метод M полиморфен, имея одно и тоже имя и сигнатуру, он существует в разных формах – для каждого класса задана собственная реализация метода. С другой стороны из-за возможностей, предоставляемых односторонним присваиванием, в точке вызова неясно, с объектом какого класса семейства в данный момент связана сущность x1 (вызову мог предшествовать такой оператор присваивания if(B) x1 = xk;).

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

Динамическим связыванием называется связывание цели вызова и вызываемого метода на этапе выполнения, когда с сущностью связывается метод класса объекта, связанного с сущностью в момент выполнения.

При статическом связывании метод выбирается из класса сущности, при динамическом – из класса объекта, связанного с сущностью. Понятно, что на этапе компиляции возможно только статическое связывание, поскольку только в период выполнения можно определить, с объектом какого класса связана данная сущность. Это может быть класс любого из потомков класса сущности.

Какой же из видов связывания следует применять? Статическое связывание более эффективно в реализации, поскольку может быть сделано на этапе компиляции, так что при выполнении не потребуется никаких проверок. Динамическое связывание требует накладных расходов в период выполнения. Однако во многих случаях преимущества динамического связывания столь значительны, что о затратах не стоит и беспокоиться.




Поделиться с друзьями:


Дата добавления: 2014-12-25; Просмотров: 654; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.113 сек.