Исследование MS Visual С#, страница 10

Особенности наследования в C#

В книгах, посвященных изучению основ ООП и языка С++, установилась традиция иллюстрировать реализацию главных идей и концепций ООП (механизм наследования и полиморфизм позднего связывания) на примере динамического списка полиморфных объектов. Управление таким списком позволяет наглядно продемонстрировать мощь основного оружия ООП.

В C# множественное наследование не поддерживается, поэтому класс не может иметь более одного родителя, но он может наследовать данные и методы класса и реализовывать (путем наследования) методы произвольного количества интерфейсов.

В динамическом списке, который мы собираемся создать, будут храниться адреса (теперь, в С#, вместо них используются ссылки) любых объектов из предварительно созданного иерархического дерева классов. Все они могут управляться с помощью указателя (в С# — ссылки), который имеет тип самого верхнего класса иерахии. Для отражения этого факта обычно используют мнемоническое правило: «Родители могут указывать на детей».

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

Для иллюстрации механизма наследования мы сделаем существующий класс Man абстрактным, но при этом создадим два новых класса Stud и Prof, которые будут произведены от него. Приняв такое решение, мы тем самым заявляем, что реальными объектами, ссылки на которые будут храниться в коллекции, могут быть только представители классов Stud и Prof. Абстрактный же класс Man служит лишь для создания иерархии классов: «Люди, различаемые по статусу или профессиональному признаку».

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

Задание

Создайте гетерогенный (разнородный) список объектов этих реальных классов и продемонстрируйте работу полиморфизма позднего связывания при управлении им. Введите в объявление класса Man описатель abstract, который переводит класс в разряд абстрактных.

abstract class Man : IComparable

Правила хорошего тона в области ООП диктуют: каждый объект какого-либо реального (не абстрактного) класса из иерархии классов должен уметь сообщать свое имя. Реализуем это правило в абстрактном классе Man с помощью абстрактного метода, который будет иметь осязаемое тело лишь в производных классах. Он будет возвращать строку текста с именем класса. Пусть метод называется:

abstract public string Class(); // Этот метод обязаны реализовать не абстрактные потомки

В классе Man этого описания достаточно, но в производных классах Stud и Prof оно должно быть быть более подробным и развернуто так, чтобы компилятор понял, что от него требуется. Вы помните, что описатель abstract заключает в себе смысл описателя virtual? Абстрактному методу в C# соответствует аналог в С++. Это — чисто виртуальная функция.

Учитывая сказанное, метод Class в производных классах должен иметь описатель override, который предваряет конкретную версию (продолжение) любой виртуальной функции. Например, в классе Stud.

public override string Class() { return "Stud: "; }

После этого надо решить, какие методы класса Man следует сделать виртуальными, то есть общими для всей иерархии, но имеющими свои собственные версии способа выполнения в каждом из классов. В данный момент класс Man содержит лишь один метод In() — метод ввода данных, который стоит объявить виртуальным.

public virtual void In()

{

  Console.Write("\tName: ");

  string n = Console.ReadLine();

  Name = n;

  age = (uint)Helper.AskInt ("Age: ", 1, maxAge);

}

Производные классы должны переопределить этот метод, для того, чтобы добавить свою функциональность. Например, в классе Stud этот метод может иметь такую реализацию:

public override void In()

{

  Console.Write (Class());

  base.In();

  course = (uint)Helper.AskInt("Course: ", 1, 6);

}

Отличием от С++ является способ вызова родительских версий унаследованных функций, для этого следует использовать ключевое слово base. Имя базового класса должен вычислить сам компилятор. Такое решение повышает надежность. Этот прием применяется и при объявлении конструкторов, например

public Stud() : base()

{

  course = 1;

}

Метод вывода (Out) данных класса Man уже осуществляется с помощью переопределенной (override) виртуальной функции ToString, которую, как вы помните, получает в наследство любой класс. Я думаю, что вы помните имя неявного родителя всех классов. Если забыли, то вот оно — System.Object. Именно в нем впервые определена функция:

public virtual string ToString();

В производном классе (например, классе Man) она может быть переопределена так, как показано ниже.

public override string ToString()

{

return name.PadRight(maxName) + " ; " + age;

}

В класее Stud следует продолжить модификацию метода:

public override string ToString()

{

//==== Разделители понадобятся при чтении данных из файла

  return Class() + base.ToString() + ";  Course: " + course;

}

Теперь добавим в класс Man еще один метод общего характера: метод чтения данных из файла. Он может иметь такую сигнатуру:

public virtual void Read (string line)