Интернет-журнал "Домашняя лаборатория", 2007 №9 - Журнал «Домашняя лаборатория»
Шрифт:
Интервал:
В языке C# принята следующая стратегия связывания. По умолчанию предполагается статическое связывание. Для того чтобы выполнялось динамическое связывание, метод родительского класса должен снабжаться модификатором virtual или abstract, а его потомки должны иметь модификатор override.
Три механизма, обеспечивающие полиморфизм
Под полиморфизмом в ООП понимают способность одного и того же программного текста х. M выполняться по-разному, в зависимости оттого, с каким объектом связана сущность х. Полиморфизм гарантирует, что вызываемый метод М будет принадлежать классу объекта, связанному с сущностью х.
В основе полиморфизма, характерного для семейства классов, лежат три механизма:
• одностороннее присваивание объектов внутри семейства классов; сущность, базовым классом которой является класс предка, можно связать с объектом любого из потомков. Другими словами, для введенной нами последовательности объектов хk присваивание xi = xj допустимо для всех j >=i;
• переопределение потомком метода, наследованного от родителя. Благодаря переопределению, в семействе классов существует совокупность полиморфных методов с одним именем и сигнатурой;
• динамическое связывание, позволяющее в момент выполнения вызывать метод, который принадлежит целевому объекту.
В совокупности это и называется полиморфизмом семейства классов. Целевую сущность часто называют полиморфной сущностью, вызываемый метод — полиморфным методом, сам вызов — полиморфным вызовом.
Вернемся к нашему примеру с классами Found, Derived, chiidDerived. Напомню, в родительском классе определен виртуальный метод VirtMethod и переопределен виртуальный метод Tostring родительского класса object. Потомок класса Found — класс Derived переопределяет эти методы:
public override void VirtMethod()
{
Console.WriteLine("Сын: " + this.ToString ());
public override string ToString ()
{
return(String.Format("поля: name = {0},
credit = {1},debet ={2}",name, credit, debet));
}
Потомок класса Derived — класс childDerived не создает новых полей. Поэтому он использует во многом методы родителя. Его конструктор состоит из вызова конструктора родителя:
public ChildDerived(string name, int cred, int deb):base (name,cred, deb)
{ }
Нет и переопределения метода Tostring, поскольку используется реализация родителя. А вот метод VirtMethod переопределяется:
public override void VirtMethod()
{
Console.WriteLine("внук: " + this.ToString ());
}
В классе Found определены два невиртуальных метода Nonvirtmethod и Work, наследуемые потомками Derived и ChildDerived без всяких переопределений. Вы ошибаетесь, если думаете, что работа этих методов полностью определяется базовым классом Found. Полиморфизм делает их работу куда более интересной. Давайте рассмотрим в деталях работу метода Work;
public void Work()
{
VirtMethod();
NonVirtMethod();
Analysis ();
}
При компиляции метода work будет обнаружено, что вызываемый метод VirtMethod является виртуальным, поэтому для него будет применяться динамическое связывание. Это означает, что вопрос о вызове метода откладывается до момента, когда метод Work будет вызван объектом, связанным с х. Объект может принадлежать как классу Found, так и классам Derived и ChildDerived, в зависимости от класса объекта и будет вызван метод этого класса.
Для не виртуальных методов NonvirtMethod и Analysis будет применено статическое связывание, так что work всегда будет вызывать методы, принадлежащие классу Found. Однако и здесь не все просто. Метод NonVirtMethod
public void NonVirtMethod()
{
Console.WriteLine ("Мать: "+ this.ToString ());
}
в процессе своей работы вызывает виртуальный метод ToString. Опять-таки, для метода ToString будет применяться динамическое связывание, и в момент выполнения будет вызываться метод класса объекта.
Что же касается метода Analysis, определенного в каждом классе, то всегда в процессе работы work будет вызываться только родительский метод анализа из-за стратегии статического связывания.
Хочу обратить внимание на важный принципиальный момент. Вполне понятно, когда потомки вызывают методы родительского класса. Потомкам все известно о своих предках. Но благодаря полиморфизму методы родительского класса, в свою очередь, могут вызывать методы своих потомков, которых они совсем не знают и которые обычно и не написаны в момент создания родительского класса. Достигается это за счет того, что между родителями и потомками заключается жесткий контракт. Потомок, переопределяющий виртуальный метод, сохраняет сигнатуру метода, сохраняет атрибуты доступа, изменяя реализацию метода, но не форму его вызова.
Класс Found, создающий метод Work, говорит примерно следующее: "Я предоставляю этот метод своим потомкам. Потомок, вызвавший этот метод, должен иметь VirtMethod, выполняющий специфическую для потомка часть работы; конечно, потомок может воспользоваться и моей реализацией, но допустима и его собственная реализация. Затем часть работы выполняю я сам, но выдача информации об объекте определяется самим объектом. Заключительную часть работы, связанную с анализом, я потомкам не доверяю и делаю ее сам".
Пример работы с полиморфным семейством классов
Классы семейства с полиморфными методами уже созданы. Давайте теперь в клиентском классе Testing напишем метод, создающий объекты наших классов и вызывающий методы классов для объектов семейства:
public void TestFoundDerivedReal ()
{
Found bs = new Found ("father", 777);
Console.WriteLine("Объект bs вызывает методы класса Found");
bs.VirtMethod();
bs.NonVirtMethod ();
bs.Analysis(); bs.Work();
Derived der = new Derived("child", 888, 555);
Console.WriteLine("Объект der вызывает методы класса Derived");
der.DerivedMethod();
der.VirtMethod();
der.NonVirtMethod ();
der.Analysis();
der.Work();
ChildDerived chider = new ChildDerived("grandchild", 999, 444);
Console.WriteLine("Объект chider вызывает методы ChildDerived");
chider.VirtMethod();
chider.NonVirtMethod();
chider.Analysis(5);
chider.Work();
}
Результаты работы этого метода изображены на рис. 18.3.
Рис. 18.3. Полиморфизм семейства классов
В последующих лекциях нам неоднократно встретятся более содержательные семейства классов с полиморфизмом, так что мы сумеем еще оценить мощь этого механизма ООП.
Абстрактные классы
С наследованием тесно связан еще один важный механизм проектирования семейства классов — механизм абстрактных классов. Начну с определений.
Класс называется абстрактным, если он имеет хотя бы один абстрактный метод.
Метод называется абстрактным, если при определении метода задана его сигнатура, но не задана реализация метода.
Объявление абстрактных методов и абстрактных классов должно сопровождаться модификатором abstract. Поскольку абстрактные классы не являются полностью определенными классами, то нельзя создавать объекты абстрактных классов. Абстрактные классы могут иметь потомков, частично или полностью реализующих абстрактные методы родительского класса. Абстрактный метод чаще всего рассматривается как виртуальный метод, переопределяемый потомком, поэтому к ним применяется стратегия динамического связывания.
Абстрактные классы являются одним из важнейших инструментов объектно-ориентированного проектирования классов. К сожалению, я не
Поделиться книгой в соц сетях:
Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!